Introduction

Batch effects refer to sources of unwanted variation that are unrelated to and can obscure the biological factors of interest. Handling batch effects is a necessary step to improve the reproducibility of research studies. However, methods developed for batch effects often rely on strong assumptions about data characteristics and the types of batch effects.

This hands-on practice provides guidelines for batch effect detection, management, and evaluation of method effectiveness through visual and numerical approaches. Although the practice will illustrate the workflow using microbiome data, the concepts and techniques presented are broadly applicable to any type of omics data.

Case study description

Anaerobic Digestion This study explored microbial indicators that could improve the efficacy of the Anaerobic Digestion (AD) bioprocess and prevent its failure (Chapleur et al. 2016). Samples were treated with two different ranges of phenol concentrations (effect of interest) and processed on five different dates (batch effect). This study exhibits a clear and strong batch effect with an approx. balanced batch x treatment design.

Packages installation and loading

First, let’s load the packages necessary for the analysis, and check the version of each package.

# CRAN
cran.pkgs <- c('pheatmap', 'vegan', 'ruv', 'UpSetR', 'gplots', 
               'ggplot2', 'performance', 'gridExtra')

# gridExtra:grid.arrange

# install.packages(cran.pkgs)

# Bioconductor
bioc.pkgs <- c('mixOmics', 'sva', 'limma', 'Biobase', 'metagenomeSeq', 
               'PLSDAbatch', 'TreeSummarizedExperiment')

# if(!require("BiocManager", quietly = TRUE)) install.packages("BiocManager")
# BiocManager::install(bioc.pkgs)  

# load packages 
suppressMessages(suppressWarnings(sapply(c(cran.pkgs, bioc.pkgs), require, 
                                         character.only = TRUE)))
                pheatmap                    vegan                      ruv 
                    TRUE                     TRUE                     TRUE 
                  UpSetR                   gplots                  ggplot2 
                    TRUE                     TRUE                     TRUE 
             performance                gridExtra                 mixOmics 
                    TRUE                     TRUE                     TRUE 
                     sva                    limma                  Biobase 
                    TRUE                     TRUE                     TRUE 
           metagenomeSeq               PLSDAbatch TreeSummarizedExperiment 
                    TRUE                     TRUE                     TRUE 
# print package versions
sapply(c(cran.pkgs, bioc.pkgs), package.version)
                pheatmap                    vegan                      ruv 
                "1.0.12"                 "2.6-10"                "0.9.7.1" 
                  UpSetR                   gplots                  ggplot2 
                 "1.4.0"                  "3.2.0"                  "3.5.2" 
             performance                gridExtra                 mixOmics 
                "0.13.0"                    "2.3"                 "6.30.0" 
                     sva                    limma                  Biobase 
                "3.52.0"                 "3.60.6"                 "2.64.0" 
           metagenomeSeq               PLSDAbatch TreeSummarizedExperiment 
                "1.46.0"                  "1.1.1"                 "2.12.0" 

Data pre-processing

Before detecting batch effects, the data should be properly preprocessed. The preprocessing steps may vary depending on the type of omics data. Since the example data used here are from microbiome studies, the steps below illustrate how to preprocess microbial count data.

Pre-filtering

Let’s load the AD data stored internally in PLSDAbatch R package with function data() and extract the count data using function assays() from TreeSummarizedExperiment R package.

# AD data
data('AD_data') 
ad.count <- assays(AD_data$FullData)$Count
dim(ad.count)
[1]  75 567

The raw data include 567 OTUs and 75 samples.

Then, we will use the function PreFL() ( PLSDAbatch package) to filter the data.

ad.filter.res <- PreFL(data = ad.count)
ad.filter <- ad.filter.res$data.filter
dim(ad.filter)
[1]  75 231
# zero proportion before filtering
ad.filter.res$zero.prob
[1] 0.6328042
# zero proportion after filtering
sum(ad.filter == 0)/(nrow(ad.filter) * ncol(ad.filter))
[1] 0.3806638

After filtering, 231 OTUs remained, and the proportion of zeroes decreased from 63% to 38%.

Note: The PreFL() function is only dedicated to raw counts, rather than relative abundance data. We also recommend starting the pre-filtering on raw counts, rather than on relative abundance data to mitigate compositionality issue.

In addition, don’t forget to extract the batch and treatment information out.

# extract the metadata
ad.metadata <- rowData(AD_data$FullData)

# extract the batch info
ad.batch = factor(ad.metadata$sequencing_run_date, 
                levels = unique(ad.metadata$sequencing_run_date))

# extract the treatment info
ad.trt = as.factor(ad.metadata$initial_phenol_concentration.regroup)

# add names on each 
names(ad.batch) <- names(ad.trt) <- rownames(ad.metadata)

Exercise 1: How many samples are there within each treatment and batch group?

Transformation

Prior to CLR transformation, we recommend adding 1 as an offset to the count data. Here, we will use logratio.transfo() function in mixOmics package to perform the CLR transformation.

ad.clr <- logratio.transfo(X = ad.filter, logratio = 'CLR', offset = 1) 
class(ad.clr) = 'matrix'

Batch effect detection

PCA

To visualise the data, let’s apply pca() function from mixOmics package and Scatter_Density() function from PLSDAbatch to represent the PCA sample plot with densities.

# AD data
ad.pca.before <- pca(ad.clr, ncomp = 3, scale = TRUE)

Scatter_Density(object = ad.pca.before, batch = ad.batch, trt = ad.trt, 
                title = 'AD data', trt.legend.title = 'Phenol conc.')

Exercise 2: Interpret the PCA plot created above

Boxplots and density plots

We can also visualise individual variables (OTUs in this case). For example, we can select the top OTU driving the major variance in PCA using selectVar() in mixOmics package. We can then plot this OTU as boxplots and density plots using box_plot() and density_plot() in PLSDAbatch.

ad.OTU.name <- selectVar(ad.pca.before, comp = 1)$name[1]

ad.OTU_batch <- data.frame(value = ad.clr[,ad.OTU.name], batch = ad.batch)
head(ad.OTU_batch)

box_plot(df = ad.OTU_batch, title = paste(ad.OTU.name, '(AD data)'), 
         x.angle = 30)

density_plot(df = ad.OTU_batch, title = paste(ad.OTU.name, '(AD data)'))

The boxplot and density plot indicated a strong date effect because of the differences between “14/04/2016”, “21/09/2017” and the other dates in the “OTU28”.

To assess statistical significance, we can apply a linear regression model to “OTU28” using linear_regres() from PLSDAbatch with batch and treatment effects as covariates. To compare “14/04/2016” and “21/09/2017” to the other batches, we need to set them as the reference levels, respectively, using relevel() from stats.

# reference level: 14/04/2016
ad.batch <- relevel(x = ad.batch, ref = '14/04/2016')

ad.OTU.lm <- linear_regres(data = ad.clr[,ad.OTU.name], 
                           trt = ad.trt, 
                           batch.fix = ad.batch, 
                           type = 'linear model')
summary(ad.OTU.lm$model$data)

Call:
lm(formula = data[, i] ~ trt + batch.fix)

Residuals:
    Min      1Q  Median      3Q     Max 
-1.9384 -0.3279  0.1635  0.3849  0.9887 

Coefficients:
                    Estimate Std. Error t value Pr(>|t|)    
(Intercept)           0.8501     0.2196   3.871 0.000243 ***
trt1-2               -1.6871     0.1754  -9.617 2.27e-14 ***
batch.fix09/04/2015   1.5963     0.2950   5.410 8.55e-07 ***
batch.fix01/07/2016   2.0839     0.2345   8.886 4.82e-13 ***
batch.fix14/11/2016   1.7405     0.2480   7.018 1.24e-09 ***
batch.fix21/09/2017   1.2646     0.2690   4.701 1.28e-05 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.7033 on 69 degrees of freedom
Multiple R-squared:  0.7546,    Adjusted R-squared:  0.7368 
F-statistic: 42.44 on 5 and 69 DF,  p-value: < 2.2e-16
# reference level: 21/09/2017
ad.batch <- relevel(x = ad.batch, ref = '21/09/2017')

ad.OTU.lm <- linear_regres(data = ad.clr[,ad.OTU.name], 
                           trt = ad.trt, 
                           batch.fix = ad.batch, 
                           type = 'linear model')
summary(ad.OTU.lm$model$data)

Call:
lm(formula = data[, i] ~ trt + batch.fix)

Residuals:
    Min      1Q  Median      3Q     Max 
-1.9384 -0.3279  0.1635  0.3849  0.9887 

Coefficients:
                    Estimate Std. Error t value Pr(>|t|)    
(Intercept)           2.1147     0.2502   8.453 2.97e-12 ***
trt1-2               -1.6871     0.1754  -9.617 2.27e-14 ***
batch.fix14/04/2016  -1.2646     0.2690  -4.701 1.28e-05 ***
batch.fix09/04/2015   0.3317     0.3139   1.056  0.29446    
batch.fix01/07/2016   0.8193     0.2573   3.185  0.00218 ** 
batch.fix14/11/2016   0.4759     0.2705   1.760  0.08292 .  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.7033 on 69 degrees of freedom
Multiple R-squared:  0.7546,    Adjusted R-squared:  0.7368 
F-statistic: 42.44 on 5 and 69 DF,  p-value: < 2.2e-16

We observed P < 0.001 for the regression coefficients associated with all other batches when “14/04/2016” was set as the reference level. This confirms the differences between the samples from batch “14/04/2016” and those from the other batches, as previously observed in the plots.

Exercise 3: Interpret the result when “21/09/2017” was set as the reference level

Heatmap

We can also use a heatmap to visualise the data using pheatmap package. The data first need to be scaled across both OTUs and samples.

# scale the clr data on both OTUs and samples
ad.clr.s <- scale(ad.clr, center = TRUE, scale = TRUE)
ad.clr.ss <- scale(t(ad.clr.s), center = TRUE, scale = TRUE)

ad.anno_col <- data.frame(Batch = ad.batch, Treatment = ad.trt)
ad.anno_colors <- list(Batch = color.mixo(seq_len(5)), 
                       Treatment = pb_color(seq_len(2)))
names(ad.anno_colors$Batch) = levels(ad.batch)
names(ad.anno_colors$Treatment) = levels(ad.trt)

pheatmap(ad.clr.ss, 
         cluster_rows = FALSE, 
         fontsize_row = 4, 
         fontsize_col = 6,
         fontsize = 8,
         clustering_distance_rows = 'euclidean',
         clustering_method = 'ward.D',
         treeheight_row = 30,
         annotation_col = ad.anno_col,
         annotation_colors = ad.anno_colors,
         border_color = 'NA',
         main = 'AD data - Scaled')

In the heatmap, samples from the batch dated “14/04/2016” clustered together and were distinct from the other samples, indicating a clear batch effect.

pRDA

To quantitatively evaluate batch effects, we can apply pRDA with varpart() function from vegan R package.

# AD data
ad.factors.df <- data.frame(trt = ad.trt, batch = ad.batch)
head(ad.factors.df)

ad.rda.before <- varpart(ad.clr, ~ trt, ~ batch, 
                         data = ad.factors.df, scale = TRUE)
ad.rda.before$part$indfract

In the result, X1 and X2 represent the first and second covariates fitted in the model. [a] and [b] indicate the independent proportions of variance explained by X1 and X2, respectively, while [c] represents the shared variance between X1 and X2. In the AD data, the variance explained by batch (X2) was larger than that explained by treatment (X1), with some intersection (shared variance) reflected in [c] (Adj.R.squared = 0.013). A greater shared variance suggests a more unbalanced batch × treatment design. Therefore, in this study, we considered the design to be approximately balanced.

Managing batch effects

Accounting for batch effects

The methods we use to account for batch effects include those specifically designed for microbiome data, such as the zero-inflated Gaussian (ZIG) mixture model (see the section “To go further”), as well as methods adapted for microbiome data, including linear regression, SVA (see “To go further”) and RUV4. Among these, SVA and RUV4 are designed to handle unknown batch effects.

Linear regression

Linear regression will be conducted using linear_regres() function in PLSDAbatch. We integrated the performance package, which assesses the performance of regression models, into function linear_regres(). Therefore, we can apply check_model() from performance to the outputs of linear_regres() to diagnose the validity of models fitted with treatment and batch effects for each variable (LÃŒdecke et al. 2020).

We can also extract performance metrics such as adjusted R2, RMSE, RSE, AIC and BIC for models fitted with and without batch effects, which are included in the outputs of linear_regres().

Let’s apply type = "linear model" to the AD data, given its balanced batch x treatment design.

# AD data
# ad.clr <- ad.clr[seq_len(nrow(ad.clr)), seq_len(ncol(ad.clr))]
ad.lm <- linear_regres(data = ad.clr, 
                       trt = ad.trt, 
                       batch.fix = ad.batch, 
                       type = 'linear model',
                       p.adjust.method = 'fdr')

# p values adjusted for batch effects
ad.p.adj <- ad.lm$adj.p 

check_model(ad.lm$model$OTU12)

To assess the validity of the model fitted with both treatment and batch effects, we can use diagnostic plots to check whether the assumptions are met for each microbial variable. For example, for “OTU12”, the assumptions of linearity (or homoscedasticity) and homogeneity of variance were not satisfied (top panel). No sample was classified as an outlier with a Cook’s distance greater than or equal to 0.9, however, samples “57”, “39”, “47”, “44” and “16” were relatively close to the contour lines. The correlation between batch (batch.fix) and treatment (trt) effects was very low (< 5), indicating a well-fitted model with low collinearity (middle panel). The distribution of residuals was very close to normal (bottom panel). For microbial variables that violate some model assumptions, their results should be interpreted with caution.

For the performance metrics of models fitted with or without batch effects, we show results for a subset of variables as an example only.

head(ad.lm$adj.R2)

The adjusted \(R^2\) of the model with both treatment and batch effects was higher for all the listed OTUs compared to the model with treatment effects only, suggesting that including batch effects explained more variance in the data and resulted in a better-fitting model.

We can also compare the AIC of models fitted with and without batch effects.

head(ad.lm$AIC)

A lower AIC indicates a better fit, as seen here for the model fitted with batch effects across all OTUs.

Both results strongly suggest that batch effects should be included in the linear model.

RUV4

Before applying RUV4 (RUV4() from ruv package), we need to specify negative control variables and the number of batch factors to estimate.

Empirical negative controls that are not significantly differentially abundant (adjusted P > 0.05), based on a linear regression using treatment information as the only covariate, can be used here.

Therefore, we will use a loop to fit a linear regression for each microbial variable and adjust the P values of the treatment effects for multiple comparisons using p.adjust() from stats. The empirical negative controls can then be identified based on the adjusted P values.

# empirical negative controls
ad.empir.p <- c()
for(e in seq_len(ncol(ad.clr))){
  ad.empir.lm <- lm(ad.clr[,e] ~ ad.trt)
  ad.empir.p[e] <- summary(ad.empir.lm)$coefficients[2,4]
}
ad.empir.p.adj <- p.adjust(p = ad.empir.p, method = 'fdr')
ad.nc <- ad.empir.p.adj > 0.05

The number of batch factors k can be determined using getK() function.

# estimate k
ad.k.res <- getK(Y = ad.clr, X = ad.trt, ctl = ad.nc)
ad.k <- ad.k.res$k

After all required parameters are estimated, let’s apply RUV4() with the known treatment variables, the selected negative control variables, and the estimated number of batch factors k. The resulting P values should also be adjusted for multiple comparisons.

# RUV4
ad.ruv4 <- RUV4(Y = ad.clr, X = ad.trt, ctl = ad.nc, k = ad.k) 
ad.ruv4.p <- ad.ruv4$p
ad.ruv4.p.adj <- p.adjust(ad.ruv4.p, method = "fdr")

Note: A package named RUVSeq has been developed for count data. It provides RUVg() which uses negative control variables, as well as other functions such as RUVs() and RUVr(), which use sample replicates (Moskovicz et al. 2020) or residuals from regression on treatment effects to estimate and account for latent batch effects. However, for CLR-transformed data, we still recommend using the standard ruv package.

Another method, SVA, which accounts for unknown batch effects, can be found in the section “To go further”.

Correcting for batch effects

The methods we will use to correct for batch effects include ComBat, PLSDA-batch and RUVIII. Among these, RUVIII is designed to correct for unknown batch effects. Other methods, such as removeBatchEffect, sPLSDA-batch and percentile normalisation, can be found in the section “To go further”.

ComBat

The ComBat() function (from sva package) supports both parametric and non-parametric correction, controlled by the option par.prior. For parametric adjustment, the model’s validity can be assessed by setting prior.plots = T (Leek et al. 2012).

For AD data, we apply a non-parametric correction (par.prior = FALSE), using the input batch grouping information (batch) and the treatment design matrix (mod) to calculate the batch effect corrected data ad.ComBat. We chose the non-parametric approach based on the assumption that microbial abundance data, even after CLR transformation, do not follow a standard distribution.

# the treatment design matrix
ad.mod <- model.matrix( ~ ad.trt)
ad.ComBat <- t(ComBat(t(ad.clr), batch = ad.batch, 
                      mod = ad.mod, par.prior = FALSE))
G3;Found5batches
gG3;Adjusting for1covariate(s) or covariate level(s)
gG3;Standardizing Data across genes
gG3;Fitting L/S model and finding priors
gG3;Finding nonparametric adjustments
gG3;Adjusting the Data

g

PLSDA-batch

Before applying PLSDA-batch, we need to specify the optimal number of components related to treatment (ncomp.trt) and batch effects (ncomp.bat).

To determine ncomp.trt, we will use the plsda() from mixOmics with only the treatment grouping information to estimate the optimal number of treatment-related components to retain.

# estimate the number of treatment components
ad.trt.tune <- plsda(X = ad.clr, Y = ad.trt, ncomp = 5)
ad.trt.tune$prop_expl_var #1
$X
     comp1      comp2      comp3      comp4      comp5 
0.18619506 0.07873817 0.08257396 0.09263497 0.06594757 

$Y
     comp1      comp2      comp3      comp4      comp5 
1.00000000 0.33857374 0.17315267 0.10551296 0.08185822 

We should choose the number of components that explain 100% of the variance in the outcome matrix Y. From the result, 1 component was sufficient to preserve the treatment information.

To determine ncomp.bat, we will use the PLSDA_batch() function (PLSDAbatch package) with both treatment and batch grouping information, along with the specified number of treatment-related components, to estimate the optimal number of batch components to remove.

# estimate the number of batch components
ad.batch.tune <- PLSDA_batch(X = ad.clr, 
                             Y.trt = ad.trt, Y.bat = ad.batch,
                             ncomp.trt = 1, ncomp.bat = 10)
ad.batch.tune$explained_variance.bat 
$X
     comp1      comp2      comp3      comp4      comp5      comp6      comp7      comp8 
0.17470922 0.11481264 0.10122717 0.07507395 0.03919879 0.03703988 0.03446079 0.02810058 
     comp9     comp10 
0.02097436 0.01398721 

$Y
      comp1       comp2       comp3       comp4       comp5       comp6       comp7 
0.247465374 0.261574081 0.230138238 0.260822307 0.230187383 0.267943709 0.256012373 
      comp8       comp9      comp10 
0.241124438 0.004732098 0.234942402 
sum(ad.batch.tune$explained_variance.bat$Y[seq_len(4)]) #4
[1] 1

Using the same criterion as for selecting treatment components, we will choose the number of batch-related components that explain 100% of the variance in the batch outcome matrix (Y.bat). According to the result, 4 components are required to remove the batch effects.

Then let’s correct for batch effects by applying PLSDA_batch() with the input treatment and batch grouping information, along with the estimated optimal numbers of related components.

ad.PLSDA_batch.res <- PLSDA_batch(X = ad.clr, 
                                  Y.trt = ad.trt, Y.bat = ad.batch,
                                  ncomp.trt = 1, ncomp.bat = 4)
ad.PLSDA_batch <- ad.PLSDA_batch.res$X.nobatch

Note: Comparatively, PLSDA-batch (PLSDAbatch package) is more suitable for weak batch effects, while from the same package, sparse PLSDA-batch is better suited for strong batch effects (see section “To go further”), weighted PLSDA-batch is specifically designed for unbalanced but not nested batch x treatment designs.

RUVIII

The RUVIII() function is from ruv package. Similar to RUV4(), it requires empirical negative control variables and the number of unwanted factors (k) to remove. We will use those estimated in the RUV4 section. In addition, it requires sample replicates, which should be structured into a mapping matrix using replicate.matrix(). With these elements, we can then obtain the batch effect corrected data by applying RUVIII().

ad.replicates <- ad.metadata$sample_name.data.extraction
head(table(ad.replicates, ad.batch))
             ad.batch
ad.replicates 09/04/2015 14/04/2016 01/07/2016
       E1aJ0           0          0          1
       E1aJ16          1          0          0
       E1aJ40          0          1          0
       E1aJ5           0          1          0
       E1bJ16          0          0          1
       E1bJ40          0          0          1
             ad.batch
ad.replicates 14/11/2016 21/09/2017
       E1aJ0           0          0
       E1aJ16          0          0
       E1aJ40          0          1
       E1aJ5           0          0
       E1bJ16          0          0
       E1bJ40          0          0
ad.replicates.matrix <- replicate.matrix(ad.replicates)

ad.RUVIII <- RUVIII(Y = ad.clr, M = ad.replicates.matrix, 
                    ctl = ad.nc, k = ad.k)
rownames(ad.RUVIII) <- rownames(ad.clr)

Assessing batch effect correction

Methods that detect batch effects

PCA

First, we can compare the PCA sample plots before and after batch effect correction using different methods.

ad.pca.before <- pca(ad.clr, ncomp = 3, scale = TRUE)
ad.pca.ComBat <- pca(ad.ComBat, ncomp = 3, scale = TRUE)
ad.pca.PLSDA_batch <- pca(ad.PLSDA_batch, ncomp = 3, scale = TRUE)
ad.pca.RUVIII <- pca(ad.RUVIII, ncomp = 3, scale = TRUE)
# order batches
ad.batch = factor(ad.metadata$sequencing_run_date, 
                  levels = unique(ad.metadata$sequencing_run_date))

ad.pca.before.plot <- Scatter_Density(object = ad.pca.before, 
                                      batch = ad.batch, 
                                      trt = ad.trt, 
                                      title = 'Before correction')

ad.pca.ComBat.plot <- Scatter_Density(object = ad.pca.ComBat, 
                                      batch = ad.batch, 
                                      trt = ad.trt, 
                                      title = 'ComBat')

ad.pca.PLSDA_batch.plot <- Scatter_Density(object = ad.pca.PLSDA_batch, 
                                           batch = ad.batch, 
                                           trt = ad.trt, 
                                           title = 'PLSDA-batch')

ad.pca.RUVIII.plot <- Scatter_Density(object = ad.pca.RUVIII, 
                                      batch = ad.batch, 
                                      trt = ad.trt, 
                                      title = 'RUVIII')

Exercise 4: Interpret the PCA plots created above

We can also compare boxplots and density plots of key variables identified by PCA, as well as heatmaps that highlight distinct patterns before and after batch effect correction. However, due to time limitations, we will not show the results here.

pRDA

As a quantitative measure, we can calculate the global explained variance across all microbial variables using pRDA. The results can be visualised using partVar_plot() from PLSDAbatch package.

# arrange data before and after batch effect correction into a list
ad.corrected.list <- list(`Before correction` = ad.clr, 
                          ComBat = ad.ComBat, 
                          `PLSDA-batch` = ad.PLSDA_batch, 
                          RUVIII = ad.RUVIII)

ad.prop.df <- data.frame(Treatment = NA, Batch = NA, 
                         Intersection = NA, 
                         Residuals = NA) 

# run rda in a loop
for(i in seq_len(length(ad.corrected.list))){
  rda.res = varpart(ad.corrected.list[[i]], ~ trt, ~ batch,
                    data = ad.factors.df, scale = TRUE)
  ad.prop.df[i, ] <- rda.res$part$indfract$Adj.R.squared}

rownames(ad.prop.df) = names(ad.corrected.list)

# change the order of explained variance
ad.prop.df <- ad.prop.df[, c(1,3,2,4)]

# remove values less than zero, and recalculate the proportions
ad.prop.df[ad.prop.df < 0] = 0
ad.prop.df <- as.data.frame(t(apply(ad.prop.df, 1, 
                                    function(x){x/sum(x)})))

partVar_plot(prop.df = ad.prop.df)

As shown in the figure above, the intersection between batch and treatment variance was small (1.3%), indicating that the batch x treatment design is not highly unbalanced. As a result, unweighted PLSDA-batch remained applicable, and the weighted version was not used. Among all methods, the data corrected with PLSDA-batch showed the best performance, explaining a higher proportion of treatment variance compared to the others. Notably, replicates in AD data are not present across all batches, highlighting the critical role of sample replicates in the effectiveness of RUVIII. Therefore, we calculated pseudo replicates and recalculated the batch effect corrected data. The result showed clear improvement, which can be found in the section “To go further”.

Other methods

\(\mathbf{R^2}\)

We can also calculate the \(R^2\) from one-way ANOVA for each variable to evaluate the proportion of explained variance, using lm() from stats package. To ensure comparability of \(R^2\) values across variables, we will scale the corrected data prior to the \(R^2\) calculation. The results will then be visualised by ggplot() from ggplot2 R package.

# AD data
# scale
ad.corr_scale.list <- lapply(ad.corrected.list, 
                             function(x){apply(x, 2, scale)})

# perform one-way ANOVA on each variable within each dataset
ad.r_values.list <- list()
for(i in seq_len(length(ad.corr_scale.list))){
  # for each dataset
  ad.r_values <- data.frame(trt = NA, batch = NA)
  for(c in seq_len(ncol(ad.corr_scale.list[[i]]))){
    # for each variable
    ad.fit.res.trt <- lm(ad.corr_scale.list[[i]][,c] ~ ad.trt)
    ad.r_values[c,1] <- summary(ad.fit.res.trt)$r.squared
    ad.fit.res.batch <- lm(ad.corr_scale.list[[i]][,c] ~ ad.batch)
    ad.r_values[c,2] <- summary(ad.fit.res.batch)$r.squared
  }
  ad.r_values.list[[i]] <- ad.r_values
}
names(ad.r_values.list) <- names(ad.corr_scale.list)
head(ad.r_values.list$`Before correction`)

# generate boxplots for each dataset
ad.boxp.list <- list()
for(i in seq_len(length(ad.r_values.list))){
  ad.boxp.list[[i]] <- 
    data.frame(r2 = c(ad.r_values.list[[i]][ ,'trt'],
                      ad.r_values.list[[i]][ ,'batch']), 
               Effects = as.factor(rep(c('Treatment','Batch'), 
                                       each = 231)))
}
names(ad.boxp.list) <- names(ad.r_values.list)
head(ad.boxp.list$`Before correction`)

ad.r2.boxp <- rbind(ad.boxp.list$`Before correction`,
                    ad.boxp.list$ComBat,
                    ad.boxp.list$`PLSDA-batch`,
                    ad.boxp.list$RUVIII)

ad.r2.boxp$methods <- rep(c('Before correction', 
                            'ComBat','PLSDA-batch',
                            'RUVIII'), each = 462)

ad.r2.boxp$methods <- factor(ad.r2.boxp$methods, 
                             levels = unique(ad.r2.boxp$methods))
head(ad.r2.boxp)

ggplot(ad.r2.boxp, aes(x = Effects, y = r2, fill = Effects)) +
  geom_boxplot(alpha = 0.80) +
  theme_bw() + 
  theme(text = element_text(size = 18),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        axis.text.x = element_text(angle = 60, hjust = 1, size = 18),
        axis.text.y = element_text(size = 18),
        panel.grid.minor.x = element_blank(),
        panel.grid.major.x = element_blank(),
        legend.position = "right") + facet_grid( ~ methods) + 
  scale_fill_manual(values=pb_color(c(12,14))) 

As shown in the plots, the data corrected using ComBat still contained a few variables with a large proportion of batch variance. In the case of RUVIII, the corrected data exhibited a greater proportion of batch variance than treatment variance.

Alignment scores

Before applying alignment_score() function from PLSDAbatch, we need to specify the proportion of data variance to explain (var), the number of nearest neighbours (k) and the number of PCs to calculate (ncomp) for the internal PCA. We can then use ggplot() function from ggplot2 to visualise the results.

# calculate the alignment scores
ad.scores <- c()
names(ad.batch) <- rownames(ad.clr)
for(i in seq_len(length(ad.corrected.list))){
  res <- alignment_score(data = ad.corrected.list[[i]], 
                         batch = ad.batch, 
                         var = 0.95, 
                         k = 8, 
                         ncomp = 50)
  ad.scores <- c(ad.scores, res)
}
head(ad.scores)
[1] 0.3766892 0.7179054 0.6689189 0.5304054
# rearrange the data for ggplot
ad.scores.df <- data.frame(scores = ad.scores, 
                           methods = names(ad.corrected.list))

ad.scores.df$methods <- factor(ad.scores.df$methods, 
                               levels = rev(names(ad.corrected.list)))

head(ad.scores.df)

ggplot() + geom_col(aes(x = ad.scores.df$methods, 
                        y = ad.scores.df$scores)) + 
  geom_text(aes(x = ad.scores.df$methods, 
                y = ad.scores.df$scores/2, 
                label = round(ad.scores.df$scores, 3)), 
            size = 3, col = 'white') + 
  coord_flip() + theme_bw() + ylab('Alignment Scores') + 
  xlab('') + ylim(0,0.85)

The alignment scores complement the PCA results, especially when batch effect removal is difficult to evaluate from PCA sample plots alone. For example, in the previous PCA plots (see section “PCA”), we observed that samples from different batches appeared more intermixed after batch effect correction, regardless of the method used. However, comparing the performance of different methods remained challenging.

Since a higher alignment score indicates better mixing of samples across batches, the bar plot above shows that ComBat achieved superior performance compared to the other methods. However, the \(R^2\) analysis revealed that the data corrected with ComBat still contained a few variables with a large proportion of batch variance (see section “\(R^2\)”). This highlights the importance of evaluating correction effectiveness using multiple techniques to ensure an unbiased assessment.

In this example, the lower alignment score observed for the data corrected using PLSDA-batch compared to ComBat may be due to differences in the PCA sample projections. Specifically, the data corrected with ComBat exhibited greater variance in its PCA projection, whereas the data with PLSDA-batch showed smaller variance. Smaller variance in the projection can lead to lower alignment scores, as it increases the likelihood that samples from different batches appear as nearest neighbours. Nonetheless, the pRDA results (see section “pRDA”) quantitatively confirmed that PLSDA-batch effectively removed batch variance entirely (Wang and Lê Cao 2023).

Discussion and conclusions

This workshop presents a comprehensive framework for managing batch effects, using microbiome data as an illustrative example. The input for this workflow is preprocessed data. For different types of omics data, the preprocessing steps may vary.

To detect batch effects, both visual tools (e.g., PCA, boxplots, density plots, and heatmaps) and quantitative methods such as pRDA can be applied. If batch effects appear negligible, for instance, when pRDA shows only a minimal proportion of variance explained by batch or when PCA does not reveal clear batch driven clusters, batch effect management may not be necessary. However, when batch effects are substantial, two strategies can be considered: accounting for batch effects during modeling, or removing them from the data prior to analysis.

Both strategies assume a balanced batch × treatment design. If the design is nested, batch effects can only be accounted for using a linear mixed model. If the design is unbalanced but not nested, batch effects can either be controlled using standard linear models or removed using weighted PLSDA-batch.

In most cases, batch grouping information is assumed to be known. When it is not, batch estimation methods such as RUV4, RUVIII and SVA can be employed. RUV-based methods rely on negative control variables and/or sample replicates, while SVA estimates batch effects from variables that are least affected by treatment (see section “To go further”). However, for RUV methods, these negative controls and sample replicates must capture the full spectrum of batch variation; otherwise, batch effects may not be completely addressed. We emphasised this issue in the section “Assessing batch effect correction - pRDA”, where limited replicates posed challenges for batch effect removal. Moreover, these estimation methods assume that batch effects are independent of treatment. When batch and treatment are correlated, the batch effect cannot be accurately estimated and may lead to spurious associations.

Additionally, most methods for batch effect management assume systematic batch effects across variables, and some models are highly influenced by this assumption (e.g., SVA, ComBat and RUV methods). Therefore, we recommend validating the model before applying it.

The next critical step is to evaluate the effectiveness of batch effect management. While such evaluation is often implicit in methods that account for batch effects, it is essential for methods that remove them. This can be done by comparing the data before and after correction using the same tools employed for batch effect detection. Additionally, \(R^2\) values can be calculated for each microbial variable to quantify the variance explained by batch and treatment, and alignment scores can be used to assess how well samples from different batches are mixed. However, individual evaluation tools may have limitations. For example, PCA relies on visual interpretation, while alignment scores focus solely on distances in the PCA projection space. Therefore, a robust conclusion should be based on multiple complementary evaluation methods.

Once batch effects have been adequately addressed, downstream analyses, such as multivariate discriminant analysis or univariate differential analysis, can be performed. Among these, multivariate methods are often more appropriate for microbiome data, given the natural correlations among microbial variables arising from biological interactions.

To go further

Accounting for batch effects

Methods designed for microbiome data

Zero-inflated Gaussian mixture model

To use the ZIG model, we first need to create an MRexperiment object by applying newMRexperiment() (from metagenomeSeq package) to microbiome counts and annotated data frames containing metadata and taxonomic information generated using AnnotatedDataFrame() from Biobase package.

# Creating a MRexperiment object (make sure no NA in metadata)
AD.phenotypeData = AnnotatedDataFrame(data = as.data.frame(ad.metadata))
AD.taxaData = AnnotatedDataFrame(data = as.data.frame(colData(AD_data$FullData)))
AD.obj = newMRexperiment(counts = t(ad.count), 
                         phenoData = AD.phenotypeData, 
                         featureData = AD.taxaData)
AD.obj

The AD count data are then filtered with filterData() function (from metagenomeSeq package). We can use MRcounts() to extract the count data from the MRexperiment object.

# filtering data to maintain a threshold of minimum depth or OTU presence
dim(MRcounts(AD.obj))
AD.obj = filterData(obj = AD.obj, present = 20, depth = 5)
dim(MRcounts(AD.obj))

After filtering, the AD count data were reduced to 289 OTUs and 75 samples.

We will then calculate the percentile for CSS normalisation with cumNormStatFast() function (from metagenomeSeq package). The CSS normalisation can be applied with cumNorm() and the normalised data can be exported using MRcounts() with norm = TRUE. The normalisation scaling factors for each sample, which are the sum of counts up to the calculated percentile, can be accessed through normFactors(). We can calculate the log transformed scaling factors by diving them with their median, which are better than the default scaling factors that are divided by 1000 (log2(normFactors(obj)/1000 + 1)).

# calculate the percentile for CSS normalisation
AD.pctl = cumNormStatFast(obj = AD.obj)
# CSS normalisation
AD.obj <- cumNorm(obj = AD.obj, p = AD.pctl)
# export normalised data
AD.norm.data <- MRcounts(obj = AD.obj, norm = TRUE)

# normalisation scaling factors for each sample 
AD.normFactor = normFactors(object = AD.obj)
AD.normFactor = log2(AD.normFactor/median(AD.normFactor) + 1)

After that, let’s create a design matrix with treatment variable (phenol_conc), batch variable (seq_run) and the log transformed scaling factors using model.matrix(), and then apply the ZIG model by fitZig() function. We should set useCSSoffset = FALSE to avoid using the default scaling factors as we have already included our customised scaling factor (AD.normFactor) in the design matrix.

# treatment variable
phenol_conc = pData(object = AD.obj)$initial_phenol_concentration.regroup
# batch variable
seq_run = pData(object = AD.obj)$sequencing_run_date

# build a design matrix
AD.mod.full = model.matrix(~ phenol_conc + seq_run + AD.normFactor)

# settings for the fitZig() function
AD.settings <- zigControl(maxit = 10, verbose = TRUE)

# apply the ZIG model
ADfit <- fitZig(obj = AD.obj, mod = AD.mod.full, 
                useCSSoffset = FALSE, control = AD.settings)

The OTUs with the top 50 smallest p values can be extracted using MRcoefs(). Let’s set eff = 0.5, so only the OTUs with at least “0.5” quantile (50%) number of effective samples (positive samples + estimated undersampling zeroes) are extracted.

ADcoefs <- MRcoefs(ADfit, coef = 2, group = 3, number = 50, eff = 0.5)
head(ADcoefs)

Methods adapted for microbiome data

SVA

SVA only accounts for unknown batch effects. Therefore, we assume that the batch grouping information in the AD data is unknown. We first need to build two design matrices with (ad.mod) and without (ad.mod0) treatment grouping information generated with model.matrix() function from stats. We then need to use num.sv() from sva package to determine the number of batch variables n.sv that is used to estimate batch effects in function sva().

# estimate batch effects
ad.mod <- model.matrix( ~ ad.trt)
ad.mod0 <- model.matrix( ~ 1, data = ad.trt)
ad.sva.n <- num.sv(dat = t(ad.clr), mod = ad.mod, method = 'leek')
ad.sva <- sva(t(ad.clr), ad.mod, ad.mod0, n.sv = ad.sva.n)
Number of significant surrogate variables is:  4 
Iteration (out of 5 ):1  2  3  4  5  

The estimated batch effects are then input into f.pvalue() to calculate the P-values of treatment effects. The estimated batch effects in SVA are assumed to be independent of the treatment effects. However, SVA can tolerate some sort of correlation between batch and treatment effects, but only to a limited extent (Wang and LêCao 2020).

# include estimated batch effects in the linear model
ad.mod.batch <- cbind(ad.mod, ad.sva$sv)
ad.mod0.batch <- cbind(ad.mod0, ad.sva$sv)
ad.sva.p <- f.pvalue(t(ad.clr), ad.mod.batch, ad.mod0.batch)
ad.sva.p.adj <- p.adjust(ad.sva.p, method = 'fdr')

Correcting for batch effects

removeBatchEffect

Before applying removeBatchEffect, we need to prepare a design matrix (design) that includes the treatment grouping information, which will be preserved during batch effect correction. This design matrix can be generated using model.matrix() function from stats.

Then we can use removeBatchEffect() function (limma package) with input batch grouping information (batch) and treatment design matrix (design) to calculate batch effect corrected data ad.rBE.

ad.mod <- model.matrix( ~ ad.trt)
ad.rBE <- t(removeBatchEffect(t(ad.clr), batch = ad.batch, 
                              design = ad.mod))

sPLSDA-batch

To apply sPLSDA-batch, we will use the same function PLSDA_batch(), but we need to specify the number of variables to select on each component (usually only treatment-related components keepX.trt). To determine the optimal number of variables to select, we will use tune.splsda() function from mixOmics package (Rohart et al. 2017) with all possible numbers of variables to select for each component (test.keepX).

# estimate the number of variables to select per treatment component
set.seed(777)
ad.test.keepX = c(seq(1, 10, 1), seq(20, 100, 10), 
                  seq(150, 231, 50), 231)
ad.trt.tune.v <- tune.splsda(X = ad.clr, Y = ad.trt, 
                             ncomp = 1, test.keepX = ad.test.keepX, 
                             validation = 'Mfold', folds = 4, 
                             nrepeat = 50)
ad.trt.tune.v$choice.keepX #100

Here, the optimal number of variables to select for the treatment component was 100. Since we have adjusted the amount of treatment variation to preserve, we need to re-estimate the optimal number of components related to batch effects using the same criterion mentioned in section “PLSDA-batch”.

# estimate the number of batch components
ad.batch.tune <- PLSDA_batch(X = ad.clr, 
                             Y.trt = ad.trt, Y.bat = ad.batch,
                             ncomp.trt = 1, keepX.trt = 100,
                             ncomp.bat = 10)
ad.batch.tune$explained_variance.bat #4
$X
     comp1      comp2      comp3      comp4      comp5      comp6 
0.17420018 0.11477097 0.09813477 0.07894965 0.03744028 0.03625205 
     comp7      comp8      comp9     comp10 
0.03696400 0.02622186 0.02347569 0.01481321 

$Y
    comp1     comp2     comp3     comp4     comp5     comp6     comp7 
0.2474775 0.2606716 0.2301081 0.2617429 0.2327513 0.2550725 0.2468812 
    comp8     comp9    comp10 
0.1405583 0.1247367 0.2547368 
sum(ad.batch.tune$explained_variance.bat$Y[seq_len(4)])
[1] 1

According to the result, we needed 4 batch-related components to remove batch variance from the data with function PLSDA_batch().

ad.sPLSDA_batch.res <- PLSDA_batch(X = ad.clr, 
                                   Y.trt = ad.trt, Y.bat = ad.batch,
                                   ncomp.trt = 1, keepX.trt = 100,
                                   ncomp.bat = 4)
ad.sPLSDA_batch <- ad.sPLSDA_batch.res$X.nobatch

Note: for unbalanced batch x treatment design (with the exception of the nested design), we can specify balance = FALSE in PLSDA_batch() function to apply weighted PLSDA-batch.

Percentile Normalisation

For PN correction, let’s apply percentile_norm() function from PLSDAbatch package and specify a control group (ctrl.grp).

ad.PN <- percentile_norm(data = ad.clr, batch = ad.batch, 
                         trt = ad.trt, ctrl.grp = '0-0.5')

RUVIII with pseudo replicates

# add group mean as the pseudo replicates
ad_group_mean <- matrix(ncol = ncol(ad.clr))
ad_mean_trt_lab <- c()
ad_mean_batch_lab <- c()
for(te in levels(ad.trt)){
  for(be in levels(ad.batch)){
    ad_group_mean <- rbind(ad_group_mean,apply(ad.clr[ad.trt == te & ad.batch == be, ], 2, mean))
    ad_mean_trt_lab <- c(ad_mean_trt_lab, te)
    ad_mean_batch_lab <- c(ad_mean_batch_lab, be)
  }
}
ad_group_mean <- ad_group_mean[-1,]
ad_new_ind <- rep(c("mean1", "mean2"), each = 5)


ad.replicates.new <- as.factor(c(as.character(ad.metadata$sample_name.data.extraction), ad_new_ind))
ad.replicates.matrix.new <- replicate.matrix(ad.replicates.new)
ad.clr.new <- rbind(ad.clr, ad_group_mean)
ad.trt.new <- as.factor(c(as.character(ad.trt), ad_mean_trt_lab))
ad.batch.new <- as.factor(c(as.character(ad.batch), ad_mean_batch_lab))

# empirical negative controls
ad.empir.p.new <- c()
for(e in seq_len(ncol(ad.clr.new))){
  ad.empir.lm <- lm(ad.clr.new[,e] ~ ad.trt.new)
  ad.empir.p.new[e] <- summary(ad.empir.lm)$coefficients[2,4]
}
ad.empir.p.adj.new <- p.adjust(p = ad.empir.p.new, method = 'fdr')
ad.nc.new <- ad.empir.p.adj.new > 0.05

# estimate k
ad.k.res.new <- getK(Y = ad.clr.new, X = ad.trt.new, ctl = ad.nc.new)
ad.k.new <- ad.k.res.new$k

# RUVIII
ad.RUVIII.new <- RUVIII(Y = ad.clr.new, 
                    M = ad.replicates.matrix.new, 
                    ctl = ad.nc.new, k = ad.k.new)
rownames(ad.RUVIII.new) <- rownames(ad.clr.new)

Assessing batch effect correction

PCA

First, we can compare the PCA sample plots before and after batch effect correction with different methods.

ad.pca.before <- pca(ad.clr, ncomp = 3, scale = TRUE)
ad.pca.rBE <- pca(ad.rBE, ncomp = 3, scale = TRUE)
ad.pca.ComBat <- pca(ad.ComBat, ncomp = 3, scale = TRUE)
ad.pca.PLSDA_batch <- pca(ad.PLSDA_batch, ncomp = 3, scale = TRUE)
ad.pca.sPLSDA_batch <- pca(ad.sPLSDA_batch, ncomp = 3, scale = TRUE)
ad.pca.PN <- pca(ad.PN, ncomp = 3, scale = TRUE)
ad.pca.RUVIII <- pca(ad.RUVIII, ncomp = 3, scale = TRUE)
ad.pca.RUVIII.new <- pca(ad.RUVIII.new, ncomp = 3, scale = TRUE)
# order batches
ad.batch = factor(ad.metadata$sequencing_run_date, 
                  levels = unique(ad.metadata$sequencing_run_date))

ad.pca.before.plot <- Scatter_Density(object = ad.pca.before, 
                                      batch = ad.batch, 
                                      trt = ad.trt, 
                                      title = 'Before correction')

ad.pca.rBE.plot <- Scatter_Density(object = ad.pca.rBE, 
                                   batch = ad.batch, 
                                   trt = ad.trt, 
                                   title = 'removeBatchEffect')

ad.pca.ComBat.plot <- Scatter_Density(object = ad.pca.ComBat, 
                                      batch = ad.batch, 
                                      trt = ad.trt, 
                                      title = 'ComBat')

ad.pca.PLSDA_batch.plot <- Scatter_Density(object = ad.pca.PLSDA_batch, 
                                           batch = ad.batch, 
                                           trt = ad.trt, 
                                           title = 'PLSDA-batch')

ad.pca.sPLSDA_batch.plot <- Scatter_Density(object = ad.pca.sPLSDA_batch, 
                                            batch = ad.batch, 
                                            trt = ad.trt, 
                                            title = 'sPLSDA-batch')

ad.pca.PN.plot <- Scatter_Density(object = ad.pca.PN, 
                                  batch = ad.batch, 
                                  trt = ad.trt, 
                                  title = 'Percentile Normalisation')

ad.pca.RUVIII.plot <- Scatter_Density(object = ad.pca.RUVIII, 
                                      batch = ad.batch, 
                                      trt = ad.trt, 
                                      title = 'RUVIII')

ad.pca.RUVIII.new.plot <- Scatter_Density(object = ad.pca.RUVIII.new, 
                                      batch = ad.batch.new, 
                                      trt = ad.trt.new, 
                                      title = 'RUVIII+pseR')

pRDA


ad.corrected.list <- list(`Before correction` = ad.clr, 
                          removeBatchEffect = ad.rBE, 
                          ComBat = ad.ComBat, 
                          `PLSDA-batch` = ad.PLSDA_batch, 
                          `sPLSDA-batch` = ad.sPLSDA_batch, 
                          `Percentile Normalisation` = ad.PN,
                          RUVIII = ad.RUVIII)

ad.prop.df <- data.frame(Treatment = NA, Batch = NA, 
                         Intersection = NA, 
                         Residuals = NA) 
for(i in seq_len(length(ad.corrected.list))){
  rda.res = varpart(ad.corrected.list[[i]], ~ trt, ~ batch,
                    data = ad.factors.df, scale = TRUE)
  ad.prop.df[i, ] <- rda.res$part$indfract$Adj.R.squared}

rownames(ad.prop.df) = names(ad.corrected.list)

ad.prop.df <- ad.prop.df[, c(1,3,2,4)]

ad.prop.df[ad.prop.df < 0] = 0
ad.prop.df <- as.data.frame(t(apply(ad.prop.df, 1, 
                                    function(x){x/sum(x)})))

# RUVIII + pseudo replicates
rda.ruviii = varpart(ad.RUVIII.new, ~ ad.trt.new, ~ ad.batch.new, scale = TRUE)
RUVIII.prop = rda.ruviii$part$indfract$Adj.R.squared[c(1,3,2,4)]

RUVIII.prop[RUVIII.prop < 0] = 0
RUVIII.prop <- RUVIII.prop/sum(RUVIII.prop)

ad.prop.df <- rbind(ad.prop.df, `RUVIII+pseR` = RUVIII.prop)

partVar_plot(prop.df = ad.prop.df)

Session Info

sessionInfo()

References

Chapleur, Olivier, Céline Madigou, Raphaël Civade, Yohan Rodolphe, Laurent Mazéas, and Théodore Bouchez. 2016. “Increasing Concentrations of Phenol Progressively Affect Anaerobic Digestion of Cellulose and Associated Microbial Communities.” Biodegradation 27 (1): 15–27.
LÃŒdecke, Daniel, Dominique Makowski, Philip Waggoner, and Indrajeet Patil. 2020. “Performance: Assessment of Regression Models Performance.” CRAN. https://doi.org/10.5281/zenodo.3952174.
Leek, Jeffrey T, W Evan Johnson, Hilary S Parker, Andrew E Jaffe, and John D Storey. 2012. “The Sva Package for Removing Batch Effects and Other Unwanted Variation in High-Throughput Experiments.” Bioinformatics 28 (6): 882–83.
Moskovicz, Veronica, Rina Ben-El, Guy Horev, and Boaz Mizrahi. 2020. “Skin Microbiota Dynamics Following b. Subtilis Formulation Challenge.”
Rohart, Florian, Benoı̂t Gautier, Amrit Singh, and Kim-Anh Lê Cao. 2017. “mixOmics: An r Package for ‘Omics Feature Selection and Multiple Data Integration.” PLoS Computational Biology 13 (11): e1005752.
Wang, Yiwen, and Kim-Anh Lê Cao. 2023. “PLSDA-Batch: A Multivariate Framework to Correct for Batch Effects in Microbiome Data.” Briefings in Bioinformatics 24 (2): bbac622.
Wang, Yiwen, and Kim-Anh LêCao. 2020. “Managing Batch Effects in Microbiome Data.” Briefings in Bioinformatics 21 (6): 1954–70.
LS0tCnRpdGxlOiAiQmF0Y2ggZWZmZWN0IG1hbmFnZW1lbnQiCmF1dGhvcjogIkV2YSIKZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJWQgJUIgJVknKWAiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIHRvY19kZXB0aDogJzMnCiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDMKICAgIHRvY19mbG9hdDogeWVzCiAgcGRmX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogMwplZGl0b3Jfb3B0aW9uczogCiAgbWFya2Rvd246IAogICAgd3JhcDogNzIKYmlibGlvZ3JhcGh5OiByZWZlcmVuY2VzLmJpYgotLS0KCmBgYHs9aHRtbH0KPCEtLQpTaG93IC8gaGlkZSBhbnN3ZXJzIHRvIGV4ZXJjaXNlcy4KQ29kZSBhZGFwdGVkIGZyb206IGh0dHBzOi8vY2hyaXNiZWVsZXkubmV0Lz9wPTExMDQKLS0+CmBgYApgYGB7PWh0bWx9CjxzY3JpcHQ+CmZ1bmN0aW9uIG15RnVuY3Rpb24oaWQpIHsKICAgIHZhciB4ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoaWQpOwogICAgaWYgKHguc3R5bGUuZGlzcGxheSA9PT0gIm5vbmUiKSB7CiAgICAgICAgeC5zdHlsZS5kaXNwbGF5ID0gImJsb2NrIjsKICAgIH0gZWxzZSB7CiAgICAgICAgeC5zdHlsZS5kaXNwbGF5ID0gIm5vbmUiOwogICAgfQp9Cjwvc2NyaXB0PgpgYGAKYGBgez1odG1sfQo8c3R5bGU+CmRpdiAuaW5mbyB7CiAgbWFyZ2luOiBhdXRvOwogIGJhY2tncm91bmQtY29sb3I6ICNFQUYwRkI7CiAgd2lkdGg6IDk1JTsKICBwYWRkaW5nOiAxMHB4Owp9Cjwvc3R5bGU+CmBgYApgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0KIyBTbWFsbGVyIGltYWdlcyBmb3IgcGRmCiMga25pdHI6Om9wdHNfY2h1bmskc2V0KG91dC53aWR0aD0iNTAlIikKb3B0aW9ucyh3aWR0aD04MCkKYGBgCgojIEludHJvZHVjdGlvbgoKCkJhdGNoIGVmZmVjdHMgcmVmZXIgdG8gc291cmNlcyBvZiB1bndhbnRlZCB2YXJpYXRpb24gdGhhdCBhcmUgdW5yZWxhdGVkIHRvIGFuZCBjYW4gb2JzY3VyZSB0aGUgYmlvbG9naWNhbCBmYWN0b3JzIG9mIGludGVyZXN0LiBIYW5kbGluZyBiYXRjaCBlZmZlY3RzIGlzIGEgbmVjZXNzYXJ5IHN0ZXAgdG8gaW1wcm92ZSB0aGUgcmVwcm9kdWNpYmlsaXR5IG9mIHJlc2VhcmNoIHN0dWRpZXMuIEhvd2V2ZXIsIG1ldGhvZHMgZGV2ZWxvcGVkIGZvciBiYXRjaCBlZmZlY3RzIG9mdGVuIHJlbHkgb24gc3Ryb25nIGFzc3VtcHRpb25zIGFib3V0IGRhdGEgY2hhcmFjdGVyaXN0aWNzIGFuZCB0aGUgdHlwZXMgb2YgYmF0Y2ggZWZmZWN0cy4KClRoaXMgaGFuZHMtb24gcHJhY3RpY2UgcHJvdmlkZXMgZ3VpZGVsaW5lcyBmb3IgYmF0Y2ggZWZmZWN0IGRldGVjdGlvbiwgbWFuYWdlbWVudCwgYW5kIGV2YWx1YXRpb24gb2YgbWV0aG9kIGVmZmVjdGl2ZW5lc3MgdGhyb3VnaCB2aXN1YWwgYW5kIG51bWVyaWNhbCBhcHByb2FjaGVzLiBBbHRob3VnaCB0aGUgcHJhY3RpY2Ugd2lsbCBpbGx1c3RyYXRlIHRoZSB3b3JrZmxvdyB1c2luZyBtaWNyb2Jpb21lIGRhdGEsIHRoZSBjb25jZXB0cyBhbmQgdGVjaG5pcXVlcyBwcmVzZW50ZWQgYXJlIGJyb2FkbHkgYXBwbGljYWJsZSB0byBhbnkgdHlwZSBvZiBvbWljcyBkYXRhLgoKCiMjIENhc2Ugc3R1ZHkgZGVzY3JpcHRpb24KCioqQW5hZXJvYmljIERpZ2VzdGlvbioqIFRoaXMgc3R1ZHkgZXhwbG9yZWQgbWljcm9iaWFsIGluZGljYXRvcnMgdGhhdCBjb3VsZCBpbXByb3ZlIHRoZSBlZmZpY2FjeSBvZiB0aGUgQW5hZXJvYmljIERpZ2VzdGlvbiAoQUQpIGJpb3Byb2Nlc3MgYW5kIHByZXZlbnQgaXRzIGZhaWx1cmUgW0BjaGFwbGV1cjIwMTZpbmNyZWFzaW5nXS4gU2FtcGxlcyB3ZXJlIHRyZWF0ZWQgd2l0aCB0d28gZGlmZmVyZW50IHJhbmdlcyBvZiBwaGVub2wgY29uY2VudHJhdGlvbnMgIChlZmZlY3Qgb2YgaW50ZXJlc3QpIGFuZCBwcm9jZXNzZWQgb24gZml2ZSBkaWZmZXJlbnQgZGF0ZXMgKGJhdGNoIGVmZmVjdCkuIFRoaXMgc3R1ZHkgZXhoaWJpdHMgYSBjbGVhciBhbmQgc3Ryb25nIGJhdGNoIGVmZmVjdCB3aXRoIGFuIGFwcHJveC4gYmFsYW5jZWQgYmF0Y2ggeCB0cmVhdG1lbnQgZGVzaWduLiAKCiMjIFBhY2thZ2VzIGluc3RhbGxhdGlvbiBhbmQgbG9hZGluZwoKRmlyc3QsIGxldCdzIGxvYWQgdGhlIHBhY2thZ2VzIG5lY2Vzc2FyeSBmb3IgdGhlIGFuYWx5c2lzLCBhbmQgY2hlY2sgdGhlIHZlcnNpb24gb2YgZWFjaCBwYWNrYWdlLgoKYGBge3J9CiMgQ1JBTgpjcmFuLnBrZ3MgPC0gYygncGhlYXRtYXAnLCAndmVnYW4nLCAncnV2JywgJ1VwU2V0UicsICdncGxvdHMnLCAKICAgICAgICAgICAgICAgJ2dncGxvdDInLCAncGVyZm9ybWFuY2UnLCAnZ3JpZEV4dHJhJykKCiMgZ3JpZEV4dHJhOmdyaWQuYXJyYW5nZQoKIyBpbnN0YWxsLnBhY2thZ2VzKGNyYW4ucGtncykKCiMgQmlvY29uZHVjdG9yCmJpb2MucGtncyA8LSBjKCdtaXhPbWljcycsICdzdmEnLCAnbGltbWEnLCAnQmlvYmFzZScsICdtZXRhZ2Vub21lU2VxJywgCiAgICAgICAgICAgICAgICdQTFNEQWJhdGNoJywgJ1RyZWVTdW1tYXJpemVkRXhwZXJpbWVudCcpCgojIGlmKCFyZXF1aXJlKCJCaW9jTWFuYWdlciIsIHF1aWV0bHkgPSBUUlVFKSkgaW5zdGFsbC5wYWNrYWdlcygiQmlvY01hbmFnZXIiKQojIEJpb2NNYW5hZ2VyOjppbnN0YWxsKGJpb2MucGtncykgIAoKIyBsb2FkIHBhY2thZ2VzIApzdXBwcmVzc01lc3NhZ2VzKHN1cHByZXNzV2FybmluZ3Moc2FwcGx5KGMoY3Jhbi5wa2dzLCBiaW9jLnBrZ3MpLCByZXF1aXJlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjaGFyYWN0ZXIub25seSA9IFRSVUUpKSkKCiMgcHJpbnQgcGFja2FnZSB2ZXJzaW9ucwpzYXBwbHkoYyhjcmFuLnBrZ3MsIGJpb2MucGtncyksIHBhY2thZ2UudmVyc2lvbikKYGBgCgojIERhdGEgcHJlLXByb2Nlc3NpbmcKCkJlZm9yZSBkZXRlY3RpbmcgYmF0Y2ggZWZmZWN0cywgdGhlIGRhdGEgc2hvdWxkIGJlIHByb3Blcmx5IHByZXByb2Nlc3NlZC4gVGhlIHByZXByb2Nlc3Npbmcgc3RlcHMgbWF5IHZhcnkgZGVwZW5kaW5nIG9uIHRoZSB0eXBlIG9mIG9taWNzIGRhdGEuIFNpbmNlIHRoZSBleGFtcGxlIGRhdGEgdXNlZCBoZXJlIGFyZSBmcm9tIG1pY3JvYmlvbWUgc3R1ZGllcywgdGhlIHN0ZXBzIGJlbG93IGlsbHVzdHJhdGUgaG93IHRvIHByZXByb2Nlc3MgbWljcm9iaWFsIGNvdW50IGRhdGEuCgojIyBQcmUtZmlsdGVyaW5nCgpMZXQncyBsb2FkIHRoZSAqKkFEIGRhdGEqKiBzdG9yZWQgaW50ZXJuYWxseSBpbiAqUExTREFiYXRjaCogUiBwYWNrYWdlIHdpdGggZnVuY3Rpb24gYGRhdGEoKWAgYW5kIGV4dHJhY3QgdGhlIGNvdW50IGRhdGEgdXNpbmcgZnVuY3Rpb24gYGFzc2F5cygpYCBmcm9tICpUcmVlU3VtbWFyaXplZEV4cGVyaW1lbnQqIFIgcGFja2FnZS4KCmBgYHtyfQojIEFEIGRhdGEKZGF0YSgnQURfZGF0YScpIAphZC5jb3VudCA8LSBhc3NheXMoQURfZGF0YSRGdWxsRGF0YSkkQ291bnQKZGltKGFkLmNvdW50KQpgYGAKClRoZSByYXcgZGF0YSBpbmNsdWRlIDU2NyBPVFVzIGFuZCA3NSBzYW1wbGVzLiAKClRoZW4sIHdlIHdpbGwgdXNlIHRoZSBmdW5jdGlvbiBgUHJlRkwoKWAgKCAqUExTREFiYXRjaCogcGFja2FnZSkgdG8gZmlsdGVyIHRoZSBkYXRhLgoKCmBgYHtyfQphZC5maWx0ZXIucmVzIDwtIFByZUZMKGRhdGEgPSBhZC5jb3VudCkKYWQuZmlsdGVyIDwtIGFkLmZpbHRlci5yZXMkZGF0YS5maWx0ZXIKZGltKGFkLmZpbHRlcikKCiMgemVybyBwcm9wb3J0aW9uIGJlZm9yZSBmaWx0ZXJpbmcKYWQuZmlsdGVyLnJlcyR6ZXJvLnByb2IKIyB6ZXJvIHByb3BvcnRpb24gYWZ0ZXIgZmlsdGVyaW5nCnN1bShhZC5maWx0ZXIgPT0gMCkvKG5yb3coYWQuZmlsdGVyKSAqIG5jb2woYWQuZmlsdGVyKSkKYGBgCgpBZnRlciBmaWx0ZXJpbmcsIDIzMSBPVFVzIHJlbWFpbmVkLCBhbmQgdGhlIHByb3BvcnRpb24gb2YgemVyb2VzIGRlY3JlYXNlZCBmcm9tIDYzJSB0byAzOCUuCgpOb3RlOiBUaGUgYFByZUZMKClgIGZ1bmN0aW9uIGlzIG9ubHkgZGVkaWNhdGVkIHRvIHJhdyBjb3VudHMsIHJhdGhlciB0aGFuIHJlbGF0aXZlIGFidW5kYW5jZSBkYXRhLiBXZSBhbHNvIHJlY29tbWVuZCBzdGFydGluZyB0aGUgcHJlLWZpbHRlcmluZyBvbiByYXcgY291bnRzLCByYXRoZXIgdGhhbiBvbiByZWxhdGl2ZSBhYnVuZGFuY2UgZGF0YSB0byBtaXRpZ2F0ZSBjb21wb3NpdGlvbmFsaXR5IGlzc3VlLiAgCgpJbiBhZGRpdGlvbiwgZG9uJ3QgZm9yZ2V0IHRvIGV4dHJhY3QgdGhlIGJhdGNoIGFuZCB0cmVhdG1lbnQgaW5mb3JtYXRpb24gb3V0LgoKYGBge3J9CiMgZXh0cmFjdCB0aGUgbWV0YWRhdGEKYWQubWV0YWRhdGEgPC0gcm93RGF0YShBRF9kYXRhJEZ1bGxEYXRhKQoKIyBleHRyYWN0IHRoZSBiYXRjaCBpbmZvCmFkLmJhdGNoID0gZmFjdG9yKGFkLm1ldGFkYXRhJHNlcXVlbmNpbmdfcnVuX2RhdGUsIAogICAgICAgICAgICAgICAgbGV2ZWxzID0gdW5pcXVlKGFkLm1ldGFkYXRhJHNlcXVlbmNpbmdfcnVuX2RhdGUpKQoKIyBleHRyYWN0IHRoZSB0cmVhdG1lbnQgaW5mbwphZC50cnQgPSBhcy5mYWN0b3IoYWQubWV0YWRhdGEkaW5pdGlhbF9waGVub2xfY29uY2VudHJhdGlvbi5yZWdyb3VwKQoKIyBhZGQgbmFtZXMgb24gZWFjaCAKbmFtZXMoYWQuYmF0Y2gpIDwtIG5hbWVzKGFkLnRydCkgPC0gcm93bmFtZXMoYWQubWV0YWRhdGEpCmBgYAoKIyMjIyBFeGVyY2lzZSAxOiBIb3cgbWFueSBzYW1wbGVzIGFyZSB0aGVyZSB3aXRoaW4gZWFjaCB0cmVhdG1lbnQgYW5kIGJhdGNoIGdyb3VwPwoKPGJ1dHRvbiBvbmNsaWNrPSJteUZ1bmN0aW9uKCYjMzk7cTEmIzM5OykiPgoKU2hvdyBzb2x1dGlvbnMKCjwvYnV0dG9uPgoKOjo6IHsjcTEgc3R5bGU9ImRpc3BsYXk6bm9uZSJ9CgojIyMjIEFuc3dlcgoKYGBge3J9Cmxlbmd0aChhZC5iYXRjaCkKc3VtbWFyeShhZC5iYXRjaCkKCmxlbmd0aChhZC50cnQpCnN1bW1hcnkoYWQudHJ0KQoKdGFibGUoYWQuYmF0Y2gsIGFkLnRydCkKYGBgClRoZSB0cmVhdG1lbnQgYW5kIGJhdGNoIGdyb3VwaW5nIGluZm9ybWF0aW9uIGNvcnJlc3BvbmRzIHRvIHRoZSBzYW1lIHNhbXBsZXMgYXMgdGhlIGNvdW50IGRhdGEsIHdoaWNoIGlzIHJlcXVpcmVkIGZvciBkb3duc3RyZWFtIGFuYWx5c2lzLiBXZSBhbHNvIGV4YW1pbmVkIHRoZSBzYW1wbGUgc2l6ZXMgd2l0aGluIGVhY2ggdHJlYXRtZW50IGFuZCBiYXRjaCBncm91cCB0byBhc3Nlc3Mgd2hldGhlciB0aGUgYmF0Y2ggw5cgdHJlYXRtZW50IGRlc2lnbiBpcyBiYWxhbmNlZC4gSW4gdGhlICoqQUQgZGF0YSoqLCB0aGUgZGVzaWduIGlzIGFwcHJveGltYXRlbHkgYmFsYW5jZWQuCjo6Ogo8IS0tIGVuZCBzb2x1dGlvbnMgLS0+CgojIyBUcmFuc2Zvcm1hdGlvbgoKUHJpb3IgdG8gQ0xSIHRyYW5zZm9ybWF0aW9uLCB3ZSByZWNvbW1lbmQgYWRkaW5nIDEgYXMgYW4gb2Zmc2V0IHRvIHRoZSBjb3VudCBkYXRhLiBIZXJlLCB3ZSB3aWxsIHVzZSBgbG9ncmF0aW8udHJhbnNmbygpYCBmdW5jdGlvbiBpbiAqbWl4T21pY3MqIHBhY2thZ2UgdG8gcGVyZm9ybSB0aGUgQ0xSIHRyYW5zZm9ybWF0aW9uLgoKYGBge3J9CmFkLmNsciA8LSBsb2dyYXRpby50cmFuc2ZvKFggPSBhZC5maWx0ZXIsIGxvZ3JhdGlvID0gJ0NMUicsIG9mZnNldCA9IDEpIApjbGFzcyhhZC5jbHIpID0gJ21hdHJpeCcKYGBgCgoKIyBCYXRjaCBlZmZlY3QgZGV0ZWN0aW9uCgojIyBQQ0EKClRvIHZpc3VhbGlzZSB0aGUgZGF0YSwgbGV0J3MgYXBwbHkgYHBjYSgpYCBmdW5jdGlvbiBmcm9tICptaXhPbWljcyogcGFja2FnZSBhbmQgYFNjYXR0ZXJfRGVuc2l0eSgpYCBmdW5jdGlvbiBmcm9tICpQTFNEQWJhdGNoKiB0byByZXByZXNlbnQgdGhlIFBDQSBzYW1wbGUgcGxvdCB3aXRoIGRlbnNpdGllcy4KCgpgYGB7ciBBRHBjYUJlZm9yZSwgZmlnLmFsaWduID0gJ2NlbnRlcicsIGZpZy5jYXAgPSAnVGhlIFBDQSBzYW1wbGUgcGxvdCB3aXRoIGRlbnNpdGllcyBpbiB0aGUgQUQgZGF0YS4nfQojIEFEIGRhdGEKYWQucGNhLmJlZm9yZSA8LSBwY2EoYWQuY2xyLCBuY29tcCA9IDMsIHNjYWxlID0gVFJVRSkKClNjYXR0ZXJfRGVuc2l0eShvYmplY3QgPSBhZC5wY2EuYmVmb3JlLCBiYXRjaCA9IGFkLmJhdGNoLCB0cnQgPSBhZC50cnQsIAogICAgICAgICAgICAgICAgdGl0bGUgPSAnQUQgZGF0YScsIHRydC5sZWdlbmQudGl0bGUgPSAnUGhlbm9sIGNvbmMuJykKYGBgCgojIyMjIEV4ZXJjaXNlIDI6IEludGVycHJldCB0aGUgUENBIHBsb3QgY3JlYXRlZCBhYm92ZQoKPGJ1dHRvbiBvbmNsaWNrPSJteUZ1bmN0aW9uKCYjMzk7cTImIzM5OykiPgoKU2hvdyBzb2x1dGlvbnMKCjwvYnV0dG9uPgoKOjo6IHsjcTIgc3R5bGU9ImRpc3BsYXk6bm9uZSJ9CgojIyMjIEFuc3dlcgpJbiB0aGUgZmlndXJlIGFib3ZlLCB3ZSBvYnNlcnZlZCAxKSBhIGNsZWFyIGRpc3RpbmN0aW9uIGJldHdlZW4gc2FtcGxlcyB0cmVhdGVkIHdpdGggZGlmZmVyZW50IHBoZW5vbCBjb25jZW50cmF0aW9ucyBhbmQgMikgZGlmZmVyZW5jZXMgYmV0d2VlbiBzYW1wbGVzIHNlcXVlbmNlZCBvbiAiMTQvMDQvMjAxNiIsICIyMS8wOS8yMDE3IiBhbmQgdGhlIG90aGVyIGRhdGVzLiBUaGVyZWZvcmUsIHRoZSBiYXRjaCBlZmZlY3QgcmVsYXRlZCB0byBzZXF1ZW5jaW5nIGRhdGVzIG5lZWRzIHRvIGJlIHJlbW92ZWQuCjo6Ogo8IS0tIGVuZCBzb2x1dGlvbnMgLS0+CgoKIyMgQm94cGxvdHMgYW5kIGRlbnNpdHkgcGxvdHMKCldlIGNhbiBhbHNvIHZpc3VhbGlzZSBpbmRpdmlkdWFsIHZhcmlhYmxlcyAoT1RVcyBpbiB0aGlzIGNhc2UpLiBGb3IgZXhhbXBsZSwgd2UgY2FuIHNlbGVjdCB0aGUgdG9wIE9UVSBkcml2aW5nIHRoZSBtYWpvciB2YXJpYW5jZSBpbiBQQ0EgdXNpbmcgYHNlbGVjdFZhcigpYCBpbiAqbWl4T21pY3MqIHBhY2thZ2UuIFdlIGNhbiB0aGVuIHBsb3QgdGhpcyBPVFUgYXMgYm94cGxvdHMgYW5kIGRlbnNpdHkgcGxvdHMgdXNpbmcgYGJveF9wbG90KClgIGFuZCBgZGVuc2l0eV9wbG90KClgIGluICpQTFNEQWJhdGNoKi4KCgpgYGB7ciBBRGJveEJlZm9yZSwgb3V0LndpZHRoID0gJzYwJScsIGZpZy5hbGlnbiA9ICdjZW50ZXInLCBmaWcuY2FwID0gJ0JveHBsb3RzIG9mIHNhbXBsZSB2YWx1ZXMgaW4gIk9UVTI4IiBiZWZvcmUgYmF0Y2ggZWZmZWN0IGNvcnJlY3Rpb24gaW4gdGhlIEFEIGRhdGEuJ30KYWQuT1RVLm5hbWUgPC0gc2VsZWN0VmFyKGFkLnBjYS5iZWZvcmUsIGNvbXAgPSAxKSRuYW1lWzFdCgphZC5PVFVfYmF0Y2ggPC0gZGF0YS5mcmFtZSh2YWx1ZSA9IGFkLmNsclssYWQuT1RVLm5hbWVdLCBiYXRjaCA9IGFkLmJhdGNoKQpoZWFkKGFkLk9UVV9iYXRjaCkKCmJveF9wbG90KGRmID0gYWQuT1RVX2JhdGNoLCB0aXRsZSA9IHBhc3RlKGFkLk9UVS5uYW1lLCAnKEFEIGRhdGEpJyksIAogICAgICAgICB4LmFuZ2xlID0gMzApCmBgYAoKYGBge3IgQURkZW5zaXR5QmVmb3JlLCBvdXQud2lkdGggPSAnNjAlJywgZmlnLmFsaWduID0gJ2NlbnRlcicsIGZpZy5jYXAgPSAnRGVuc2l0eSBwbG90cyBvZiBzYW1wbGUgdmFsdWVzIGluICJPVFUyOCIgYmVmb3JlIGJhdGNoIGVmZmVjdCBjb3JyZWN0aW9uIGluIHRoZSBBRCBkYXRhLid9CmRlbnNpdHlfcGxvdChkZiA9IGFkLk9UVV9iYXRjaCwgdGl0bGUgPSBwYXN0ZShhZC5PVFUubmFtZSwgJyhBRCBkYXRhKScpKQpgYGAKClRoZSBib3hwbG90IGFuZCBkZW5zaXR5IHBsb3QgaW5kaWNhdGVkIGEgc3Ryb25nIGRhdGUgZWZmZWN0IGJlY2F1c2Ugb2YgdGhlIGRpZmZlcmVuY2VzIGJldHdlZW4gIjE0LzA0LzIwMTYiLCAiMjEvMDkvMjAxNyIgYW5kIHRoZSBvdGhlciBkYXRlcyBpbiB0aGUgIk9UVTI4Ii4KClRvIGFzc2VzcyBzdGF0aXN0aWNhbCBzaWduaWZpY2FuY2UsIHdlIGNhbiBhcHBseSBhIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIHRvICJPVFUyOCIgdXNpbmcgYGxpbmVhcl9yZWdyZXMoKWAgZnJvbSAqUExTREFiYXRjaCogd2l0aCBiYXRjaCBhbmQgdHJlYXRtZW50IGVmZmVjdHMgYXMgY292YXJpYXRlcy4gVG8gY29tcGFyZSAiMTQvMDQvMjAxNiIgYW5kICIyMS8wOS8yMDE3IiAgdG8gdGhlIG90aGVyIGJhdGNoZXMsIHdlIG5lZWQgdG8gc2V0IHRoZW0gYXMgdGhlIHJlZmVyZW5jZSBsZXZlbHMsIHJlc3BlY3RpdmVseSwgdXNpbmcgYHJlbGV2ZWwoKWAgZnJvbSAqc3RhdHMqLgoKCmBgYHtyfQojIHJlZmVyZW5jZSBsZXZlbDogMTQvMDQvMjAxNgphZC5iYXRjaCA8LSByZWxldmVsKHggPSBhZC5iYXRjaCwgcmVmID0gJzE0LzA0LzIwMTYnKQoKYWQuT1RVLmxtIDwtIGxpbmVhcl9yZWdyZXMoZGF0YSA9IGFkLmNsclssYWQuT1RVLm5hbWVdLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJ0ID0gYWQudHJ0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgYmF0Y2guZml4ID0gYWQuYmF0Y2gsIAogICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gJ2xpbmVhciBtb2RlbCcpCnN1bW1hcnkoYWQuT1RVLmxtJG1vZGVsJGRhdGEpCgojIHJlZmVyZW5jZSBsZXZlbDogMjEvMDkvMjAxNwphZC5iYXRjaCA8LSByZWxldmVsKHggPSBhZC5iYXRjaCwgcmVmID0gJzIxLzA5LzIwMTcnKQoKYWQuT1RVLmxtIDwtIGxpbmVhcl9yZWdyZXMoZGF0YSA9IGFkLmNsclssYWQuT1RVLm5hbWVdLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJ0ID0gYWQudHJ0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgYmF0Y2guZml4ID0gYWQuYmF0Y2gsIAogICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gJ2xpbmVhciBtb2RlbCcpCnN1bW1hcnkoYWQuT1RVLmxtJG1vZGVsJGRhdGEpCmBgYAoKV2Ugb2JzZXJ2ZWQgUCA8IDAuMDAxIGZvciB0aGUgcmVncmVzc2lvbiBjb2VmZmljaWVudHMgYXNzb2NpYXRlZCB3aXRoIGFsbCBvdGhlciBiYXRjaGVzIHdoZW4gIjE0LzA0LzIwMTYiIHdhcyBzZXQgYXMgdGhlIHJlZmVyZW5jZSBsZXZlbC4gVGhpcyBjb25maXJtcyB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiB0aGUgc2FtcGxlcyBmcm9tIGJhdGNoICIxNC8wNC8yMDE2IiBhbmQgdGhvc2UgZnJvbSB0aGUgb3RoZXIgYmF0Y2hlcywgYXMgcHJldmlvdXNseSBvYnNlcnZlZCBpbiB0aGUgcGxvdHMuCgojIyMjIEV4ZXJjaXNlIDM6IEludGVycHJldCB0aGUgcmVzdWx0IHdoZW4gIjIxLzA5LzIwMTciIHdhcyBzZXQgYXMgdGhlIHJlZmVyZW5jZSBsZXZlbAoKPGJ1dHRvbiBvbmNsaWNrPSJteUZ1bmN0aW9uKCYjMzk7cTMmIzM5OykiPgoKU2hvdyBzb2x1dGlvbnMKCjwvYnV0dG9uPgoKOjo6IHsjcTMgc3R5bGU9ImRpc3BsYXk6bm9uZSJ9CgojIyMjIEFuc3dlcgpXaGVuICIyMS8wOS8yMDE3IiB3YXMgc2V0IGFzIHRoZSByZWZlcmVuY2UgbGV2ZWwsIHdlIG9ic2VydmVkIHNpZ25pZmljYW50IGRpZmZlcmVuY2VzIGJldHdlZW4gYmF0Y2ggIjIxLzA5LzIwMTciIGFuZCAiMTQvMDQvMjAxNiIsIGFzIHdlbGwgYXMgYmV0d2VlbiAiMjEvMDkvMjAxNyIgYW5kICIwMS8wNy8yMDE2Ii4gVGhlc2UgcmVzdWx0cyBpbmRpY2F0ZSB0aGF0IGEgYmF0Y2ggZWZmZWN0IGFzc29jaWF0ZWQgd2l0aCAiMjEvMDkvMjAxNyIgYWxzbyBleGlzdHMuCjo6Ogo8IS0tIGVuZCBzb2x1dGlvbnMgLS0+CgoKCiMjIEhlYXRtYXAKCldlIGNhbiBhbHNvIHVzZSBhIGhlYXRtYXAgdG8gdmlzdWFsaXNlIHRoZSBkYXRhIHVzaW5nICpwaGVhdG1hcCogcGFja2FnZS4gVGhlIGRhdGEgZmlyc3QgbmVlZCB0byBiZSBzY2FsZWQgYWNyb3NzIGJvdGggT1RVcyBhbmQgc2FtcGxlcy4KCgpgYGB7ciBBRGhlYXRtYXAsIG91dC53aWR0aCA9ICc5MCUnLCBmaWcuYWxpZ24gPSAnY2VudGVyJywgZmlnLmNhcCA9ICdIaWVyYXJjaGljYWwgY2x1c3RlcmluZyBmb3Igc2FtcGxlcyBpbiB0aGUgQUQgZGF0YS4nfQojIHNjYWxlIHRoZSBjbHIgZGF0YSBvbiBib3RoIE9UVXMgYW5kIHNhbXBsZXMKYWQuY2xyLnMgPC0gc2NhbGUoYWQuY2xyLCBjZW50ZXIgPSBUUlVFLCBzY2FsZSA9IFRSVUUpCmFkLmNsci5zcyA8LSBzY2FsZSh0KGFkLmNsci5zKSwgY2VudGVyID0gVFJVRSwgc2NhbGUgPSBUUlVFKQoKYWQuYW5ub19jb2wgPC0gZGF0YS5mcmFtZShCYXRjaCA9IGFkLmJhdGNoLCBUcmVhdG1lbnQgPSBhZC50cnQpCmFkLmFubm9fY29sb3JzIDwtIGxpc3QoQmF0Y2ggPSBjb2xvci5taXhvKHNlcV9sZW4oNSkpLCAKICAgICAgICAgICAgICAgICAgICAgICBUcmVhdG1lbnQgPSBwYl9jb2xvcihzZXFfbGVuKDIpKSkKbmFtZXMoYWQuYW5ub19jb2xvcnMkQmF0Y2gpID0gbGV2ZWxzKGFkLmJhdGNoKQpuYW1lcyhhZC5hbm5vX2NvbG9ycyRUcmVhdG1lbnQpID0gbGV2ZWxzKGFkLnRydCkKCnBoZWF0bWFwKGFkLmNsci5zcywgCiAgICAgICAgIGNsdXN0ZXJfcm93cyA9IEZBTFNFLCAKICAgICAgICAgZm9udHNpemVfcm93ID0gNCwgCiAgICAgICAgIGZvbnRzaXplX2NvbCA9IDYsCiAgICAgICAgIGZvbnRzaXplID0gOCwKICAgICAgICAgY2x1c3RlcmluZ19kaXN0YW5jZV9yb3dzID0gJ2V1Y2xpZGVhbicsCiAgICAgICAgIGNsdXN0ZXJpbmdfbWV0aG9kID0gJ3dhcmQuRCcsCiAgICAgICAgIHRyZWVoZWlnaHRfcm93ID0gMzAsCiAgICAgICAgIGFubm90YXRpb25fY29sID0gYWQuYW5ub19jb2wsCiAgICAgICAgIGFubm90YXRpb25fY29sb3JzID0gYWQuYW5ub19jb2xvcnMsCiAgICAgICAgIGJvcmRlcl9jb2xvciA9ICdOQScsCiAgICAgICAgIG1haW4gPSAnQUQgZGF0YSAtIFNjYWxlZCcpCgpgYGAKCkluIHRoZSBoZWF0bWFwLCBzYW1wbGVzIGZyb20gdGhlIGJhdGNoIGRhdGVkICIxNC8wNC8yMDE2IiBjbHVzdGVyZWQgdG9nZXRoZXIgYW5kIHdlcmUgZGlzdGluY3QgZnJvbSB0aGUgb3RoZXIgc2FtcGxlcywgaW5kaWNhdGluZyBhIGNsZWFyIGJhdGNoIGVmZmVjdC4KCgojIyBwUkRBCgpUbyBxdWFudGl0YXRpdmVseSBldmFsdWF0ZSBiYXRjaCBlZmZlY3RzLCB3ZSBjYW4gYXBwbHkgcFJEQSB3aXRoIGB2YXJwYXJ0KClgIGZ1bmN0aW9uIGZyb20gKnZlZ2FuKiBSIHBhY2thZ2UuCgpgYGB7cn0KIyBBRCBkYXRhCmFkLmZhY3RvcnMuZGYgPC0gZGF0YS5mcmFtZSh0cnQgPSBhZC50cnQsIGJhdGNoID0gYWQuYmF0Y2gpCmhlYWQoYWQuZmFjdG9ycy5kZikKCmFkLnJkYS5iZWZvcmUgPC0gdmFycGFydChhZC5jbHIsIH4gdHJ0LCB+IGJhdGNoLCAKICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBhZC5mYWN0b3JzLmRmLCBzY2FsZSA9IFRSVUUpCmFkLnJkYS5iZWZvcmUkcGFydCRpbmRmcmFjdApgYGAKCkluIHRoZSByZXN1bHQsIGBYMWAgYW5kIGBYMmAgcmVwcmVzZW50IHRoZSBmaXJzdCBhbmQgc2Vjb25kIGNvdmFyaWF0ZXMgZml0dGVkIGluIHRoZSBtb2RlbC4gYFthXWAgYW5kIGBbYl1gIGluZGljYXRlIHRoZSBpbmRlcGVuZGVudCBwcm9wb3J0aW9ucyBvZiB2YXJpYW5jZSBleHBsYWluZWQgYnkgYFgxYCBhbmQgYFgyYCwgcmVzcGVjdGl2ZWx5LCB3aGlsZSBgW2NdYCByZXByZXNlbnRzIHRoZSBzaGFyZWQgdmFyaWFuY2UgYmV0d2VlbiBgWDFgIGFuZCBgWDJgLiBJbiB0aGUgKipBRCBkYXRhKiosIHRoZSB2YXJpYW5jZSBleHBsYWluZWQgYnkgYmF0Y2ggKGBYMmApIHdhcyBsYXJnZXIgdGhhbiB0aGF0IGV4cGxhaW5lZCBieSB0cmVhdG1lbnQgKGBYMWApLCB3aXRoIHNvbWUgaW50ZXJzZWN0aW9uIChzaGFyZWQgdmFyaWFuY2UpIHJlZmxlY3RlZCBpbiBgW2NdYCAoQWRqLlIuc3F1YXJlZCA9IDAuMDEzKS4gQSBncmVhdGVyIHNoYXJlZCB2YXJpYW5jZSBzdWdnZXN0cyBhIG1vcmUgdW5iYWxhbmNlZCBiYXRjaCDDlyB0cmVhdG1lbnQgZGVzaWduLiBUaGVyZWZvcmUsIGluIHRoaXMgc3R1ZHksIHdlIGNvbnNpZGVyZWQgdGhlIGRlc2lnbiB0byBiZSBhcHByb3hpbWF0ZWx5IGJhbGFuY2VkLgoKCiMgTWFuYWdpbmcgYmF0Y2ggZWZmZWN0cwoKIyMgQWNjb3VudGluZyBmb3IgYmF0Y2ggZWZmZWN0cwoKVGhlIG1ldGhvZHMgd2UgdXNlIHRvIGFjY291bnQgZm9yIGJhdGNoIGVmZmVjdHMgaW5jbHVkZSB0aG9zZSBzcGVjaWZpY2FsbHkgZGVzaWduZWQgZm9yIG1pY3JvYmlvbWUgZGF0YSwgc3VjaCBhcyB0aGUgemVyby1pbmZsYXRlZCBHYXVzc2lhbiAoWklHKSBtaXh0dXJlIG1vZGVsIChzZWUgdGhlIHNlY3Rpb24gIlRvIGdvIGZ1cnRoZXIiKSwgYXMgd2VsbCBhcyBtZXRob2RzIGFkYXB0ZWQgZm9yIG1pY3JvYmlvbWUgZGF0YSwgaW5jbHVkaW5nIGxpbmVhciByZWdyZXNzaW9uLCBTVkEgKHNlZSAiVG8gZ28gZnVydGhlciIpIGFuZCBSVVY0LiBBbW9uZyB0aGVzZSwgU1ZBIGFuZCBSVVY0IGFyZSBkZXNpZ25lZCB0byBoYW5kbGUgdW5rbm93biBiYXRjaCBlZmZlY3RzLgoKIyMjIExpbmVhciByZWdyZXNzaW9uCgpMaW5lYXIgcmVncmVzc2lvbiB3aWxsIGJlIGNvbmR1Y3RlZCB1c2luZyBgbGluZWFyX3JlZ3JlcygpYCBmdW5jdGlvbiBpbiAqUExTREFiYXRjaCouIFdlIGludGVncmF0ZWQgdGhlICpwZXJmb3JtYW5jZSogcGFja2FnZSwgd2hpY2ggYXNzZXNzZXMgdGhlIHBlcmZvcm1hbmNlIG9mIHJlZ3Jlc3Npb24gbW9kZWxzLCBpbnRvIGZ1bmN0aW9uIGBsaW5lYXJfcmVncmVzKClgLiBUaGVyZWZvcmUsIHdlIGNhbiBhcHBseSBgY2hlY2tfbW9kZWwoKWAgZnJvbSAqcGVyZm9ybWFuY2UqIHRvIHRoZSBvdXRwdXRzIG9mIGBsaW5lYXJfcmVncmVzKClgIHRvIGRpYWdub3NlIHRoZSB2YWxpZGl0eSBvZiBtb2RlbHMgZml0dGVkIHdpdGggdHJlYXRtZW50IGFuZCBiYXRjaCBlZmZlY3RzIGZvciBlYWNoIHZhcmlhYmxlIFtAZGFuaWVsMjAyMHBlcmZvcm1hbmNlXS4gCgpXZSBjYW4gYWxzbyBleHRyYWN0IHBlcmZvcm1hbmNlIG1ldHJpY3Mgc3VjaCBhcyBhZGp1c3RlZCBSMiwgUk1TRSwgUlNFLCBBSUMgYW5kIEJJQyBmb3IgbW9kZWxzIGZpdHRlZCB3aXRoIGFuZCB3aXRob3V0IGJhdGNoIGVmZmVjdHMsIHdoaWNoIGFyZSBpbmNsdWRlZCBpbiB0aGUgb3V0cHV0cyBvZiBgbGluZWFyX3JlZ3JlcygpYC4KCkxldCdzIGFwcGx5IGB0eXBlID0gImxpbmVhciBtb2RlbCJgIHRvIHRoZSAqKkFEIGRhdGEqKiwgZ2l2ZW4gaXRzIGJhbGFuY2VkIGJhdGNoIHggdHJlYXRtZW50IGRlc2lnbi4KCgpgYGB7ciBBRGxtLCBmaWcuaGVpZ2h0ID0gMTMsIGZpZy53aWR0aCA9IDEyLCBvdXQud2lkdGggPSAnMTAwJScsIGZpZy5hbGlnbiA9ICdjZW50ZXInLCBmaWcuY2FwID0gJ0RpYWdub3N0aWMgcGxvdHMgZm9yIHRoZSBtb2RlbCBmaXR0ZWQgd2l0aCBiYXRjaCBlZmZlY3RzIG9mICJPVFUxMiIgaW4gdGhlIEFEIGRhdGEuJ30KIyBBRCBkYXRhCmFkLmxtIDwtIGxpbmVhcl9yZWdyZXMoZGF0YSA9IGFkLmNsciwgCiAgICAgICAgICAgICAgICAgICAgICAgdHJ0ID0gYWQudHJ0LCAKICAgICAgICAgICAgICAgICAgICAgICBiYXRjaC5maXggPSBhZC5iYXRjaCwgCiAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICdsaW5lYXIgbW9kZWwnLAogICAgICAgICAgICAgICAgICAgICAgIHAuYWRqdXN0Lm1ldGhvZCA9ICdmZHInKQoKIyBwIHZhbHVlcyBhZGp1c3RlZCBmb3IgYmF0Y2ggZWZmZWN0cwphZC5wLmFkaiA8LSBhZC5sbSRhZGoucCAKCmNoZWNrX21vZGVsKGFkLmxtJG1vZGVsJE9UVTEyKQpgYGAKClRvIGFzc2VzcyB0aGUgdmFsaWRpdHkgb2YgdGhlIG1vZGVsIGZpdHRlZCB3aXRoIGJvdGggdHJlYXRtZW50IGFuZCBiYXRjaCBlZmZlY3RzLCB3ZSBjYW4gdXNlIGRpYWdub3N0aWMgcGxvdHMgdG8gY2hlY2sgd2hldGhlciB0aGUgYXNzdW1wdGlvbnMgYXJlIG1ldCBmb3IgZWFjaCBtaWNyb2JpYWwgdmFyaWFibGUuIEZvciBleGFtcGxlLCBmb3IgIk9UVTEyIiwgdGhlIGFzc3VtcHRpb25zIG9mIGxpbmVhcml0eSAob3IgaG9tb3NjZWRhc3RpY2l0eSkgYW5kIGhvbW9nZW5laXR5IG9mIHZhcmlhbmNlIHdlcmUgbm90IHNhdGlzZmllZCAodG9wIHBhbmVsKS4gTm8gc2FtcGxlIHdhcyBjbGFzc2lmaWVkIGFzIGFuIG91dGxpZXIgd2l0aCBhIENvb2sncyBkaXN0YW5jZSBncmVhdGVyIHRoYW4gb3IgZXF1YWwgdG8gMC45LCBob3dldmVyLCBzYW1wbGVzICI1NyIsICIzOSIsICI0NyIsICI0NCIgYW5kICIxNiIgd2VyZSByZWxhdGl2ZWx5IGNsb3NlIHRvIHRoZSBjb250b3VyIGxpbmVzLiBUaGUgY29ycmVsYXRpb24gYmV0d2VlbiBiYXRjaCAoYGJhdGNoLmZpeGApIGFuZCB0cmVhdG1lbnQgKGB0cnRgKSBlZmZlY3RzIHdhcyB2ZXJ5IGxvdyAoPCA1KSwgaW5kaWNhdGluZyBhIHdlbGwtZml0dGVkIG1vZGVsIHdpdGggbG93IGNvbGxpbmVhcml0eSAobWlkZGxlIHBhbmVsKS4gVGhlIGRpc3RyaWJ1dGlvbiBvZiByZXNpZHVhbHMgd2FzIHZlcnkgY2xvc2UgdG8gbm9ybWFsIChib3R0b20gcGFuZWwpLiBGb3IgbWljcm9iaWFsIHZhcmlhYmxlcyB0aGF0IHZpb2xhdGUgc29tZSBtb2RlbCBhc3N1bXB0aW9ucywgdGhlaXIgcmVzdWx0cyBzaG91bGQgYmUgaW50ZXJwcmV0ZWQgd2l0aCBjYXV0aW9uLiAKCkZvciB0aGUgcGVyZm9ybWFuY2UgbWV0cmljcyBvZiBtb2RlbHMgZml0dGVkIHdpdGggb3Igd2l0aG91dCBiYXRjaCBlZmZlY3RzLCB3ZSBzaG93IHJlc3VsdHMgZm9yIGEgc3Vic2V0IG9mIHZhcmlhYmxlcyBhcyBhbiBleGFtcGxlIG9ubHkuCgoKYGBge3J9CmhlYWQoYWQubG0kYWRqLlIyKQpgYGAKClRoZSBhZGp1c3RlZCAkUl4yJCBvZiB0aGUgbW9kZWwgd2l0aCBib3RoIHRyZWF0bWVudCBhbmQgYmF0Y2ggZWZmZWN0cyB3YXMgaGlnaGVyIGZvciBhbGwgdGhlIGxpc3RlZCBPVFVzIGNvbXBhcmVkIHRvIHRoZSBtb2RlbCB3aXRoIHRyZWF0bWVudCBlZmZlY3RzIG9ubHksIHN1Z2dlc3RpbmcgdGhhdCBpbmNsdWRpbmcgYmF0Y2ggZWZmZWN0cyBleHBsYWluZWQgbW9yZSB2YXJpYW5jZSBpbiB0aGUgZGF0YSBhbmQgcmVzdWx0ZWQgaW4gYSBiZXR0ZXItZml0dGluZyBtb2RlbC4KCldlIGNhbiBhbHNvIGNvbXBhcmUgdGhlIEFJQyBvZiBtb2RlbHMgZml0dGVkIHdpdGggYW5kIHdpdGhvdXQgYmF0Y2ggZWZmZWN0cy4KCmBgYHtyfQpoZWFkKGFkLmxtJEFJQykKYGBgCgpBIGxvd2VyIEFJQyBpbmRpY2F0ZXMgYSBiZXR0ZXIgZml0LCBhcyBzZWVuIGhlcmUgZm9yIHRoZSBtb2RlbCBmaXR0ZWQgd2l0aCBiYXRjaCBlZmZlY3RzIGFjcm9zcyBhbGwgT1RVcy4KCkJvdGggcmVzdWx0cyBzdHJvbmdseSBzdWdnZXN0IHRoYXQgYmF0Y2ggZWZmZWN0cyBzaG91bGQgYmUgaW5jbHVkZWQgaW4gdGhlIGxpbmVhciBtb2RlbC4KCgojIyMgUlVWNCAKCkJlZm9yZSBhcHBseWluZyBSVVY0IChgUlVWNCgpYCBmcm9tICpydXYqIHBhY2thZ2UpLCB3ZSBuZWVkIHRvIHNwZWNpZnkgbmVnYXRpdmUgY29udHJvbCB2YXJpYWJsZXMgYW5kIHRoZSBudW1iZXIgb2YgYmF0Y2ggZmFjdG9ycyB0byBlc3RpbWF0ZS4KCkVtcGlyaWNhbCBuZWdhdGl2ZSBjb250cm9scyB0aGF0IGFyZSBub3Qgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnRpYWxseSBhYnVuZGFudCAoYWRqdXN0ZWQgUCA+IDAuMDUpLCBiYXNlZCBvbiBhIGxpbmVhciByZWdyZXNzaW9uIHVzaW5nIHRyZWF0bWVudCBpbmZvcm1hdGlvbiBhcyB0aGUgb25seSBjb3ZhcmlhdGUsIGNhbiBiZSB1c2VkIGhlcmUuCgpUaGVyZWZvcmUsIHdlIHdpbGwgdXNlIGEgbG9vcCB0byBmaXQgYSBsaW5lYXIgcmVncmVzc2lvbiBmb3IgZWFjaCBtaWNyb2JpYWwgdmFyaWFibGUgYW5kIGFkanVzdCB0aGUgUCB2YWx1ZXMgb2YgdGhlIHRyZWF0bWVudCBlZmZlY3RzIGZvciBtdWx0aXBsZSBjb21wYXJpc29ucyB1c2luZyBgcC5hZGp1c3QoKWAgZnJvbSAqc3RhdHMqLiBUaGUgZW1waXJpY2FsIG5lZ2F0aXZlIGNvbnRyb2xzIGNhbiB0aGVuIGJlIGlkZW50aWZpZWQgYmFzZWQgb24gdGhlIGFkanVzdGVkIFAgdmFsdWVzLgoKYGBge3J9CiMgZW1waXJpY2FsIG5lZ2F0aXZlIGNvbnRyb2xzCmFkLmVtcGlyLnAgPC0gYygpCmZvcihlIGluIHNlcV9sZW4obmNvbChhZC5jbHIpKSl7CiAgYWQuZW1waXIubG0gPC0gbG0oYWQuY2xyWyxlXSB+IGFkLnRydCkKICBhZC5lbXBpci5wW2VdIDwtIHN1bW1hcnkoYWQuZW1waXIubG0pJGNvZWZmaWNpZW50c1syLDRdCn0KYWQuZW1waXIucC5hZGogPC0gcC5hZGp1c3QocCA9IGFkLmVtcGlyLnAsIG1ldGhvZCA9ICdmZHInKQphZC5uYyA8LSBhZC5lbXBpci5wLmFkaiA+IDAuMDUKYGBgCgpUaGUgbnVtYmVyIG9mIGJhdGNoIGZhY3RvcnMgYGtgIGNhbiBiZSBkZXRlcm1pbmVkIHVzaW5nIGBnZXRLKClgIGZ1bmN0aW9uLgoKCmBgYHtyfQojIGVzdGltYXRlIGsKYWQuay5yZXMgPC0gZ2V0SyhZID0gYWQuY2xyLCBYID0gYWQudHJ0LCBjdGwgPSBhZC5uYykKYWQuayA8LSBhZC5rLnJlcyRrCmBgYAoKQWZ0ZXIgYWxsIHJlcXVpcmVkIHBhcmFtZXRlcnMgYXJlIGVzdGltYXRlZCwgbGV0J3MgYXBwbHkgYFJVVjQoKWAgd2l0aCB0aGUga25vd24gdHJlYXRtZW50IHZhcmlhYmxlcywgdGhlIHNlbGVjdGVkIG5lZ2F0aXZlIGNvbnRyb2wgdmFyaWFibGVzLCBhbmQgdGhlIGVzdGltYXRlZCBudW1iZXIgb2YgYmF0Y2ggZmFjdG9ycyBga2AuIFRoZSByZXN1bHRpbmcgUCB2YWx1ZXMgc2hvdWxkIGFsc28gYmUgYWRqdXN0ZWQgZm9yIG11bHRpcGxlIGNvbXBhcmlzb25zLgoKYGBge3J9CiMgUlVWNAphZC5ydXY0IDwtIFJVVjQoWSA9IGFkLmNsciwgWCA9IGFkLnRydCwgY3RsID0gYWQubmMsIGsgPSBhZC5rKSAKYWQucnV2NC5wIDwtIGFkLnJ1djQkcAphZC5ydXY0LnAuYWRqIDwtIHAuYWRqdXN0KGFkLnJ1djQucCwgbWV0aG9kID0gImZkciIpCmBgYAoKTm90ZTogQSBwYWNrYWdlIG5hbWVkICpSVVZTZXEqIGhhcyBiZWVuIGRldmVsb3BlZCBmb3IgY291bnQgZGF0YS4gSXQgcHJvdmlkZXMgYFJVVmcoKWAgd2hpY2ggdXNlcyBuZWdhdGl2ZSBjb250cm9sIHZhcmlhYmxlcywgYXMgd2VsbCBhcyBvdGhlciBmdW5jdGlvbnMgc3VjaCBhcyBgUlVWcygpYCBhbmQgYFJVVnIoKWAsIHdoaWNoIHVzZSBzYW1wbGUgcmVwbGljYXRlcyBbQG1vc2tvdmljejIwMjBza2luXSBvciByZXNpZHVhbHMgZnJvbSByZWdyZXNzaW9uIG9uIHRyZWF0bWVudCBlZmZlY3RzIHRvIGVzdGltYXRlIGFuZCBhY2NvdW50IGZvciBsYXRlbnQgYmF0Y2ggZWZmZWN0cy4gSG93ZXZlciwgZm9yIENMUi10cmFuc2Zvcm1lZCBkYXRhLCB3ZSBzdGlsbCByZWNvbW1lbmQgdXNpbmcgdGhlIHN0YW5kYXJkICpydXYqIHBhY2thZ2UuCgpBbm90aGVyIG1ldGhvZCwgU1ZBLCB3aGljaCBhY2NvdW50cyBmb3IgdW5rbm93biBiYXRjaCBlZmZlY3RzLCBjYW4gYmUgZm91bmQgaW4gdGhlIHNlY3Rpb24gIlRvIGdvIGZ1cnRoZXIiLiAKCgojIyBDb3JyZWN0aW5nIGZvciBiYXRjaCBlZmZlY3RzCgpUaGUgbWV0aG9kcyB3ZSB3aWxsIHVzZSB0byBjb3JyZWN0IGZvciBiYXRjaCBlZmZlY3RzIGluY2x1ZGUgQ29tQmF0LCBQTFNEQS1iYXRjaCBhbmQgUlVWSUlJLiBBbW9uZyB0aGVzZSwgUlVWSUlJIGlzIGRlc2lnbmVkIHRvIGNvcnJlY3QgZm9yIHVua25vd24gYmF0Y2ggZWZmZWN0cy4gT3RoZXIgbWV0aG9kcywgc3VjaCBhcyByZW1vdmVCYXRjaEVmZmVjdCwgc1BMU0RBLWJhdGNoIGFuZCBwZXJjZW50aWxlIG5vcm1hbGlzYXRpb24sIGNhbiBiZSBmb3VuZCBpbiB0aGUgc2VjdGlvbiAiVG8gZ28gZnVydGhlciIuCgojIyMgQ29tQmF0CgpUaGUgYENvbUJhdCgpYCBmdW5jdGlvbiAoZnJvbSAqc3ZhKiBwYWNrYWdlKSBzdXBwb3J0cyBib3RoIHBhcmFtZXRyaWMgYW5kIG5vbi1wYXJhbWV0cmljIGNvcnJlY3Rpb24sIGNvbnRyb2xsZWQgYnkgdGhlIG9wdGlvbiBgcGFyLnByaW9yYC4gRm9yIHBhcmFtZXRyaWMgYWRqdXN0bWVudCwgdGhlIG1vZGVsJ3MgdmFsaWRpdHkgY2FuIGJlIGFzc2Vzc2VkIGJ5IHNldHRpbmcgYHByaW9yLnBsb3RzID0gVGAgW0BsZWVrMjAxMnN2YV0uCgpGb3IgKipBRCBkYXRhKiosIHdlIGFwcGx5IGEgbm9uLXBhcmFtZXRyaWMgY29ycmVjdGlvbiAoYHBhci5wcmlvciA9IEZBTFNFYCksIHVzaW5nIHRoZSBpbnB1dCBiYXRjaCBncm91cGluZyBpbmZvcm1hdGlvbiAoYGJhdGNoYCkgYW5kIHRoZSB0cmVhdG1lbnQgZGVzaWduIG1hdHJpeCAoYG1vZGApIHRvIGNhbGN1bGF0ZSB0aGUgYmF0Y2ggZWZmZWN0IGNvcnJlY3RlZCBkYXRhIGBhZC5Db21CYXRgLiBXZSBjaG9zZSB0aGUgbm9uLXBhcmFtZXRyaWMgYXBwcm9hY2ggYmFzZWQgb24gdGhlIGFzc3VtcHRpb24gdGhhdCBtaWNyb2JpYWwgYWJ1bmRhbmNlIGRhdGEsIGV2ZW4gYWZ0ZXIgQ0xSIHRyYW5zZm9ybWF0aW9uLCBkbyBub3QgZm9sbG93IGEgc3RhbmRhcmQgZGlzdHJpYnV0aW9uLiAKCgpgYGB7cn0KIyB0aGUgdHJlYXRtZW50IGRlc2lnbiBtYXRyaXgKYWQubW9kIDwtIG1vZGVsLm1hdHJpeCggfiBhZC50cnQpCmFkLkNvbUJhdCA8LSB0KENvbUJhdCh0KGFkLmNsciksIGJhdGNoID0gYWQuYmF0Y2gsIAogICAgICAgICAgICAgICAgICAgICAgbW9kID0gYWQubW9kLCBwYXIucHJpb3IgPSBGQUxTRSkpCgpgYGAKCiMjIyBQTFNEQS1iYXRjaAoKQmVmb3JlIGFwcGx5aW5nIFBMU0RBLWJhdGNoLCB3ZSBuZWVkIHRvIHNwZWNpZnkgdGhlIG9wdGltYWwgbnVtYmVyIG9mIGNvbXBvbmVudHMgcmVsYXRlZCB0byB0cmVhdG1lbnQgKGBuY29tcC50cnRgKSBhbmQgYmF0Y2ggZWZmZWN0cyAoYG5jb21wLmJhdGApLgoKVG8gZGV0ZXJtaW5lIGBuY29tcC50cnRgLCB3ZSB3aWxsIHVzZSB0aGUgYHBsc2RhKClgIGZyb20gKm1peE9taWNzKiB3aXRoIG9ubHkgdGhlIHRyZWF0bWVudCBncm91cGluZyBpbmZvcm1hdGlvbiB0byBlc3RpbWF0ZSB0aGUgb3B0aW1hbCBudW1iZXIgb2YgdHJlYXRtZW50LXJlbGF0ZWQgY29tcG9uZW50cyB0byByZXRhaW4uCgoKYGBge3J9CiMgZXN0aW1hdGUgdGhlIG51bWJlciBvZiB0cmVhdG1lbnQgY29tcG9uZW50cwphZC50cnQudHVuZSA8LSBwbHNkYShYID0gYWQuY2xyLCBZID0gYWQudHJ0LCBuY29tcCA9IDUpCmFkLnRydC50dW5lJHByb3BfZXhwbF92YXIgIzEKYGBgCgpXZSBzaG91bGQgY2hvb3NlIHRoZSBudW1iZXIgb2YgY29tcG9uZW50cyB0aGF0IGV4cGxhaW4gMTAwJSBvZiB0aGUgdmFyaWFuY2UgaW4gdGhlIG91dGNvbWUgbWF0cml4IGBZYC4gRnJvbSB0aGUgcmVzdWx0LCAxIGNvbXBvbmVudCB3YXMgc3VmZmljaWVudCB0byBwcmVzZXJ2ZSB0aGUgdHJlYXRtZW50IGluZm9ybWF0aW9uLgoKVG8gZGV0ZXJtaW5lIGBuY29tcC5iYXRgLCB3ZSB3aWxsIHVzZSB0aGUgYFBMU0RBX2JhdGNoKClgIGZ1bmN0aW9uICgqUExTREFiYXRjaCogcGFja2FnZSkgd2l0aCBib3RoIHRyZWF0bWVudCBhbmQgYmF0Y2ggZ3JvdXBpbmcgaW5mb3JtYXRpb24sIGFsb25nIHdpdGggdGhlIHNwZWNpZmllZCBudW1iZXIgb2YgdHJlYXRtZW50LXJlbGF0ZWQgY29tcG9uZW50cywgdG8gZXN0aW1hdGUgdGhlIG9wdGltYWwgbnVtYmVyIG9mIGJhdGNoIGNvbXBvbmVudHMgdG8gcmVtb3ZlLgoKCmBgYHtyfQojIGVzdGltYXRlIHRoZSBudW1iZXIgb2YgYmF0Y2ggY29tcG9uZW50cwphZC5iYXRjaC50dW5lIDwtIFBMU0RBX2JhdGNoKFggPSBhZC5jbHIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFkudHJ0ID0gYWQudHJ0LCBZLmJhdCA9IGFkLmJhdGNoLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5jb21wLnRydCA9IDEsIG5jb21wLmJhdCA9IDEwKQphZC5iYXRjaC50dW5lJGV4cGxhaW5lZF92YXJpYW5jZS5iYXQgCnN1bShhZC5iYXRjaC50dW5lJGV4cGxhaW5lZF92YXJpYW5jZS5iYXQkWVtzZXFfbGVuKDQpXSkgIzQKYGBgCgpVc2luZyB0aGUgc2FtZSBjcml0ZXJpb24gYXMgZm9yIHNlbGVjdGluZyB0cmVhdG1lbnQgY29tcG9uZW50cywgd2Ugd2lsbCBjaG9vc2UgdGhlIG51bWJlciBvZiBiYXRjaC1yZWxhdGVkIGNvbXBvbmVudHMgdGhhdCBleHBsYWluIDEwMCUgb2YgdGhlIHZhcmlhbmNlIGluIHRoZSBiYXRjaCBvdXRjb21lIG1hdHJpeCAoYFkuYmF0YCkuIEFjY29yZGluZyB0byB0aGUgcmVzdWx0LCA0IGNvbXBvbmVudHMgYXJlIHJlcXVpcmVkIHRvIHJlbW92ZSB0aGUgYmF0Y2ggZWZmZWN0cy4KClRoZW4gbGV0J3MgY29ycmVjdCBmb3IgYmF0Y2ggZWZmZWN0cyBieSBhcHBseWluZyBgUExTREFfYmF0Y2goKWAgd2l0aCB0aGUgaW5wdXQgdHJlYXRtZW50IGFuZCBiYXRjaCBncm91cGluZyBpbmZvcm1hdGlvbiwgYWxvbmcgd2l0aCB0aGUgZXN0aW1hdGVkIG9wdGltYWwgbnVtYmVycyBvZiByZWxhdGVkIGNvbXBvbmVudHMuCgoKYGBge3J9CmFkLlBMU0RBX2JhdGNoLnJlcyA8LSBQTFNEQV9iYXRjaChYID0gYWQuY2xyLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFkudHJ0ID0gYWQudHJ0LCBZLmJhdCA9IGFkLmJhdGNoLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmNvbXAudHJ0ID0gMSwgbmNvbXAuYmF0ID0gNCkKYWQuUExTREFfYmF0Y2ggPC0gYWQuUExTREFfYmF0Y2gucmVzJFgubm9iYXRjaApgYGAKCk5vdGU6IENvbXBhcmF0aXZlbHksIFBMU0RBLWJhdGNoICgqUExTREFiYXRjaCogcGFja2FnZSkgaXMgbW9yZSBzdWl0YWJsZSBmb3Igd2VhayBiYXRjaCBlZmZlY3RzLCB3aGlsZSBmcm9tIHRoZSBzYW1lIHBhY2thZ2UsIHNwYXJzZSBQTFNEQS1iYXRjaCBpcyBiZXR0ZXIgc3VpdGVkIGZvciBzdHJvbmcgYmF0Y2ggZWZmZWN0cyAoc2VlIHNlY3Rpb24gIlRvIGdvIGZ1cnRoZXIiKSwgd2VpZ2h0ZWQgUExTREEtYmF0Y2ggaXMgc3BlY2lmaWNhbGx5IGRlc2lnbmVkIGZvciB1bmJhbGFuY2VkIGJ1dCBub3QgbmVzdGVkIGJhdGNoIHggdHJlYXRtZW50IGRlc2lnbnMuCgojIyMgUlVWSUlJCgpUaGUgYFJVVklJSSgpYCBmdW5jdGlvbiBpcyBmcm9tICpydXYqIHBhY2thZ2UuIFNpbWlsYXIgdG8gYFJVVjQoKWAsIGl0IHJlcXVpcmVzIGVtcGlyaWNhbCBuZWdhdGl2ZSBjb250cm9sIHZhcmlhYmxlcyBhbmQgdGhlIG51bWJlciBvZiB1bndhbnRlZCBmYWN0b3JzIChga2ApIHRvIHJlbW92ZS4gV2Ugd2lsbCB1c2UgdGhvc2UgZXN0aW1hdGVkIGluIHRoZSBSVVY0IHNlY3Rpb24uIEluIGFkZGl0aW9uLCBpdCByZXF1aXJlcyBzYW1wbGUgcmVwbGljYXRlcywgd2hpY2ggc2hvdWxkIGJlIHN0cnVjdHVyZWQgaW50byBhIG1hcHBpbmcgbWF0cml4IHVzaW5nIGByZXBsaWNhdGUubWF0cml4KClgLiBXaXRoIHRoZXNlIGVsZW1lbnRzLCB3ZSBjYW4gdGhlbiBvYnRhaW4gdGhlIGJhdGNoIGVmZmVjdCBjb3JyZWN0ZWQgZGF0YSBieSBhcHBseWluZyBgUlVWSUlJKClgLgoKCmBgYHtyfQphZC5yZXBsaWNhdGVzIDwtIGFkLm1ldGFkYXRhJHNhbXBsZV9uYW1lLmRhdGEuZXh0cmFjdGlvbgpoZWFkKHRhYmxlKGFkLnJlcGxpY2F0ZXMsIGFkLmJhdGNoKSkKCmFkLnJlcGxpY2F0ZXMubWF0cml4IDwtIHJlcGxpY2F0ZS5tYXRyaXgoYWQucmVwbGljYXRlcykKCmFkLlJVVklJSSA8LSBSVVZJSUkoWSA9IGFkLmNsciwgTSA9IGFkLnJlcGxpY2F0ZXMubWF0cml4LCAKICAgICAgICAgICAgICAgICAgICBjdGwgPSBhZC5uYywgayA9IGFkLmspCnJvd25hbWVzKGFkLlJVVklJSSkgPC0gcm93bmFtZXMoYWQuY2xyKQpgYGAKCgojIEFzc2Vzc2luZyBiYXRjaCBlZmZlY3QgY29ycmVjdGlvbgoKIyMgTWV0aG9kcyB0aGF0IGRldGVjdCBiYXRjaCBlZmZlY3RzCgojIyMgUENBCgpGaXJzdCwgd2UgY2FuIGNvbXBhcmUgdGhlIFBDQSBzYW1wbGUgcGxvdHMgYmVmb3JlIGFuZCBhZnRlciBiYXRjaCBlZmZlY3QgY29ycmVjdGlvbiB1c2luZyBkaWZmZXJlbnQgbWV0aG9kcy4KCmBgYHtyfQphZC5wY2EuYmVmb3JlIDwtIHBjYShhZC5jbHIsIG5jb21wID0gMywgc2NhbGUgPSBUUlVFKQphZC5wY2EuQ29tQmF0IDwtIHBjYShhZC5Db21CYXQsIG5jb21wID0gMywgc2NhbGUgPSBUUlVFKQphZC5wY2EuUExTREFfYmF0Y2ggPC0gcGNhKGFkLlBMU0RBX2JhdGNoLCBuY29tcCA9IDMsIHNjYWxlID0gVFJVRSkKYWQucGNhLlJVVklJSSA8LSBwY2EoYWQuUlVWSUlJLCBuY29tcCA9IDMsIHNjYWxlID0gVFJVRSkKYGBgCgpgYGB7ciwgZmlnLnNob3c9J2hpZGUnfQojIG9yZGVyIGJhdGNoZXMKYWQuYmF0Y2ggPSBmYWN0b3IoYWQubWV0YWRhdGEkc2VxdWVuY2luZ19ydW5fZGF0ZSwgCiAgICAgICAgICAgICAgICAgIGxldmVscyA9IHVuaXF1ZShhZC5tZXRhZGF0YSRzZXF1ZW5jaW5nX3J1bl9kYXRlKSkKCmFkLnBjYS5iZWZvcmUucGxvdCA8LSBTY2F0dGVyX0RlbnNpdHkob2JqZWN0ID0gYWQucGNhLmJlZm9yZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmF0Y2ggPSBhZC5iYXRjaCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJ0ID0gYWQudHJ0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aXRsZSA9ICdCZWZvcmUgY29ycmVjdGlvbicpCmFkLnBjYS5Db21CYXQucGxvdCA8LSBTY2F0dGVyX0RlbnNpdHkob2JqZWN0ID0gYWQucGNhLkNvbUJhdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmF0Y2ggPSBhZC5iYXRjaCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJ0ID0gYWQudHJ0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aXRsZSA9ICdDb21CYXQnKQphZC5wY2EuUExTREFfYmF0Y2gucGxvdCA8LSBTY2F0dGVyX0RlbnNpdHkob2JqZWN0ID0gYWQucGNhLlBMU0RBX2JhdGNoLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJhdGNoID0gYWQuYmF0Y2gsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJ0ID0gYWQudHJ0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpdGxlID0gJ1BMU0RBLWJhdGNoJykKYWQucGNhLlJVVklJSS5wbG90IDwtIFNjYXR0ZXJfRGVuc2l0eShvYmplY3QgPSBhZC5wY2EuUlVWSUlJLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYXRjaCA9IGFkLmJhdGNoLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cnQgPSBhZC50cnQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpdGxlID0gJ1JVVklJSScpCgpgYGAKCmBgYHtyIEFEcGNhLCBmaWcuaGVpZ2h0ID0gMTAsIGZpZy53aWR0aCA9IDEyLCBvdXQud2lkdGggPSAnMTAwJScsIGVjaG8gPSBGQUxTRSwgZmlnLmFsaWduID0gJ2NlbnRlcicsIGZpZy5jYXAgPSAnVGhlIFBDQSBzYW1wbGUgcGxvdHMgd2l0aCBkZW5zaXRpZXMgYmVmb3JlIGFuZCBhZnRlciBiYXRjaCBlZmZlY3QgY29ycmVjdGlvbiBpbiB0aGUgQUQgZGF0YS4nfQpncmlkLmFycmFuZ2UoYWQucGNhLmJlZm9yZS5wbG90LCBhZC5wY2EuQ29tQmF0LnBsb3QsIAogICAgICAgICAgICAgYWQucGNhLlBMU0RBX2JhdGNoLnBsb3QsIAogICAgICAgICAgICAgYWQucGNhLlJVVklJSS5wbG90LCBuY29sID0gMikKYGBgCgojIyMjIEV4ZXJjaXNlIDQ6IEludGVycHJldCB0aGUgUENBIHBsb3RzIGNyZWF0ZWQgYWJvdmUKCjxidXR0b24gb25jbGljaz0ibXlGdW5jdGlvbigmIzM5O3E0JiMzOTspIj4KClNob3cgc29sdXRpb25zCgo8L2J1dHRvbj4KCjo6OiB7I3E0IHN0eWxlPSJkaXNwbGF5Om5vbmUifQoKIyMjIyBBbnN3ZXIKQXMgc2hvd24gaW4gdGhlIHBsb3RzLCB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiBzYW1wbGVzIHNlcXVlbmNlZCBvbiAiMTQvMDQvMjAxNiIsICIyMS8wOS8yMDE3IiBhbmQgdGhlIG90aGVyIGRhdGVzIHdlcmUgcmVtb3ZlZCBhZnRlciBiYXRjaCBlZmZlY3QgY29ycmVjdGlvbiBieSBhbGwgbWV0aG9kcy4gQW1vbmcgdGhlc2UgbWV0aG9kcywgdGhlIGRhdGEgY29ycmVjdGVkIHdpdGggUExTREEtYmF0Y2ggcmV0YWluZWQgbW9yZSB0cmVhdG1lbnQtcmVsYXRlZCB2YXJpYXRpb24gdGhhbiB0aGUgZGF0YSBjb3JyZWN0ZWQgYnkgb3RoZXIgbWV0aG9kcywgcHJpbWFyaWx5IG9uIHRoZSBmaXJzdCBQQywgYXMgaW5kaWNhdGVkIGJ5IHRoZSB4LWF4aXMgbGFiZWwgKDI2JSkuIAo6OjoKPCEtLSBlbmQgc29sdXRpb25zIC0tPgoKV2UgY2FuIGFsc28gY29tcGFyZSBib3hwbG90cyBhbmQgZGVuc2l0eSBwbG90cyBvZiBrZXkgdmFyaWFibGVzIGlkZW50aWZpZWQgYnkgUENBLCBhcyB3ZWxsIGFzIGhlYXRtYXBzIHRoYXQgaGlnaGxpZ2h0IGRpc3RpbmN0IHBhdHRlcm5zIGJlZm9yZSBhbmQgYWZ0ZXIgYmF0Y2ggZWZmZWN0IGNvcnJlY3Rpb24uIEhvd2V2ZXIsIGR1ZSB0byB0aW1lIGxpbWl0YXRpb25zLCB3ZSB3aWxsIG5vdCBzaG93IHRoZSByZXN1bHRzIGhlcmUuCgojIyMgcFJEQQoKQXMgYSBxdWFudGl0YXRpdmUgbWVhc3VyZSwgd2UgY2FuIGNhbGN1bGF0ZSB0aGUgZ2xvYmFsIGV4cGxhaW5lZCB2YXJpYW5jZSBhY3Jvc3MgYWxsIG1pY3JvYmlhbCB2YXJpYWJsZXMgdXNpbmcgcFJEQS4gVGhlIHJlc3VsdHMgY2FuIGJlIHZpc3VhbGlzZWQgdXNpbmcgYHBhcnRWYXJfcGxvdCgpYCBmcm9tICpQTFNEQWJhdGNoKiBwYWNrYWdlLgoKYGBge3IgQURwcmRhLCBmaWcuaGVpZ2h0ID0gNiwgZmlnLmFsaWduID0gJ2NlbnRlcicsIGZpZy5jYXAgPSAnR2xvYmFsIGV4cGxhaW5lZCB2YXJpYW5jZSBiZWZvcmUgYW5kIGFmdGVyIGJhdGNoIGVmZmVjdCBjb3JyZWN0aW9uIGZvciB0aGUgQUQgZGF0YS4nfQojIGFycmFuZ2UgZGF0YSBiZWZvcmUgYW5kIGFmdGVyIGJhdGNoIGVmZmVjdCBjb3JyZWN0aW9uIGludG8gYSBsaXN0CmFkLmNvcnJlY3RlZC5saXN0IDwtIGxpc3QoYEJlZm9yZSBjb3JyZWN0aW9uYCA9IGFkLmNsciwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgQ29tQmF0ID0gYWQuQ29tQmF0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICBgUExTREEtYmF0Y2hgID0gYWQuUExTREFfYmF0Y2gsIAogICAgICAgICAgICAgICAgICAgICAgICAgIFJVVklJSSA9IGFkLlJVVklJSSkKCmFkLnByb3AuZGYgPC0gZGF0YS5mcmFtZShUcmVhdG1lbnQgPSBOQSwgQmF0Y2ggPSBOQSwgCiAgICAgICAgICAgICAgICAgICAgICAgICBJbnRlcnNlY3Rpb24gPSBOQSwgCiAgICAgICAgICAgICAgICAgICAgICAgICBSZXNpZHVhbHMgPSBOQSkgCgojIHJ1biByZGEgaW4gYSBsb29wCmZvcihpIGluIHNlcV9sZW4obGVuZ3RoKGFkLmNvcnJlY3RlZC5saXN0KSkpewogIHJkYS5yZXMgPSB2YXJwYXJ0KGFkLmNvcnJlY3RlZC5saXN0W1tpXV0sIH4gdHJ0LCB+IGJhdGNoLAogICAgICAgICAgICAgICAgICAgIGRhdGEgPSBhZC5mYWN0b3JzLmRmLCBzY2FsZSA9IFRSVUUpCiAgYWQucHJvcC5kZltpLCBdIDwtIHJkYS5yZXMkcGFydCRpbmRmcmFjdCRBZGouUi5zcXVhcmVkfQoKcm93bmFtZXMoYWQucHJvcC5kZikgPSBuYW1lcyhhZC5jb3JyZWN0ZWQubGlzdCkKCiMgY2hhbmdlIHRoZSBvcmRlciBvZiBleHBsYWluZWQgdmFyaWFuY2UKYWQucHJvcC5kZiA8LSBhZC5wcm9wLmRmWywgYygxLDMsMiw0KV0KCiMgcmVtb3ZlIHZhbHVlcyBsZXNzIHRoYW4gemVybywgYW5kIHJlY2FsY3VsYXRlIHRoZSBwcm9wb3J0aW9ucwphZC5wcm9wLmRmW2FkLnByb3AuZGYgPCAwXSA9IDAKYWQucHJvcC5kZiA8LSBhcy5kYXRhLmZyYW1lKHQoYXBwbHkoYWQucHJvcC5kZiwgMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uKHgpe3gvc3VtKHgpfSkpKQoKcGFydFZhcl9wbG90KHByb3AuZGYgPSBhZC5wcm9wLmRmKQpgYGAKCkFzIHNob3duIGluIHRoZSBmaWd1cmUgYWJvdmUsIHRoZSBpbnRlcnNlY3Rpb24gYmV0d2VlbiBiYXRjaCBhbmQgdHJlYXRtZW50IHZhcmlhbmNlIHdhcyBzbWFsbCAoMS4zJSksIGluZGljYXRpbmcgdGhhdCB0aGUgYmF0Y2ggeCB0cmVhdG1lbnQgZGVzaWduIGlzIG5vdCBoaWdobHkgdW5iYWxhbmNlZC4gQXMgYSByZXN1bHQsIHVud2VpZ2h0ZWQgUExTREEtYmF0Y2ggcmVtYWluZWQgYXBwbGljYWJsZSwgYW5kIHRoZSB3ZWlnaHRlZCB2ZXJzaW9uIHdhcyBub3QgdXNlZC4gQW1vbmcgYWxsIG1ldGhvZHMsIHRoZSBkYXRhIGNvcnJlY3RlZCB3aXRoIFBMU0RBLWJhdGNoIHNob3dlZCB0aGUgYmVzdCBwZXJmb3JtYW5jZSwgZXhwbGFpbmluZyBhIGhpZ2hlciBwcm9wb3J0aW9uIG9mIHRyZWF0bWVudCB2YXJpYW5jZSBjb21wYXJlZCB0byB0aGUgb3RoZXJzLiBOb3RhYmx5LCByZXBsaWNhdGVzIGluICoqQUQgZGF0YSoqIGFyZSBub3QgcHJlc2VudCBhY3Jvc3MgYWxsIGJhdGNoZXMsIGhpZ2hsaWdodGluZyB0aGUgY3JpdGljYWwgcm9sZSBvZiBzYW1wbGUgcmVwbGljYXRlcyBpbiB0aGUgZWZmZWN0aXZlbmVzcyBvZiBSVVZJSUkuIFRoZXJlZm9yZSwgd2UgY2FsY3VsYXRlZCBwc2V1ZG8gcmVwbGljYXRlcyBhbmQgcmVjYWxjdWxhdGVkIHRoZSBiYXRjaCBlZmZlY3QgY29ycmVjdGVkIGRhdGEuIFRoZSByZXN1bHQgc2hvd2VkIGNsZWFyIGltcHJvdmVtZW50LCB3aGljaCBjYW4gYmUgZm91bmQgaW4gdGhlIHNlY3Rpb24gIlRvIGdvIGZ1cnRoZXIiLgoKCiMjIE90aGVyIG1ldGhvZHMgeyNvdGhlci1tZXRob2RzLTF9CgojIyMgJFxtYXRoYmZ7Ul4yfSQKCldlIGNhbiBhbHNvIGNhbGN1bGF0ZSB0aGUgJFJeMiQgZnJvbSBvbmUtd2F5IEFOT1ZBIGZvciBlYWNoIHZhcmlhYmxlIHRvIGV2YWx1YXRlIHRoZSBwcm9wb3J0aW9uIG9mIGV4cGxhaW5lZCB2YXJpYW5jZSwgdXNpbmcgYGxtKClgIGZyb20gKnN0YXRzKiBwYWNrYWdlLiBUbyBlbnN1cmUgY29tcGFyYWJpbGl0eSBvZiAkUl4yJCB2YWx1ZXMgYWNyb3NzIHZhcmlhYmxlcywgd2Ugd2lsbCBzY2FsZSB0aGUgY29ycmVjdGVkIGRhdGEgcHJpb3IgdG8gdGhlICRSXjIkIGNhbGN1bGF0aW9uLiBUaGUgcmVzdWx0cyB3aWxsIHRoZW4gYmUgdmlzdWFsaXNlZCBieSBgZ2dwbG90KClgIGZyb20gKmdncGxvdDIqIFIgcGFja2FnZS4KCgpgYGB7ciBBRHIyMSwgZmlnLmhlaWdodCA9IDEwLCBmaWcud2lkdGggPSAxNCwgb3V0LndpZHRoID0gJzEwMCUnLCBmaWcuYWxpZ24gPSAnY2VudGVyJywgZmlnLmNhcCA9ICdBRCBzdHVkeTogJFJeMiQgdmFsdWVzIGZvciBlYWNoIG1pY3JvYmlhbCB2YXJpYWJsZSBiZWZvcmUgYW5kIGFmdGVyIGJhdGNoIGVmZmVjdCBjb3JyZWN0aW9uLid9CiMgQUQgZGF0YQojIHNjYWxlCmFkLmNvcnJfc2NhbGUubGlzdCA8LSBsYXBwbHkoYWQuY29ycmVjdGVkLmxpc3QsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uKHgpe2FwcGx5KHgsIDIsIHNjYWxlKX0pCgojIHBlcmZvcm0gb25lLXdheSBBTk9WQSBvbiBlYWNoIHZhcmlhYmxlIHdpdGhpbiBlYWNoIGRhdGFzZXQKYWQucl92YWx1ZXMubGlzdCA8LSBsaXN0KCkKZm9yKGkgaW4gc2VxX2xlbihsZW5ndGgoYWQuY29ycl9zY2FsZS5saXN0KSkpewogICMgZm9yIGVhY2ggZGF0YXNldAogIGFkLnJfdmFsdWVzIDwtIGRhdGEuZnJhbWUodHJ0ID0gTkEsIGJhdGNoID0gTkEpCiAgZm9yKGMgaW4gc2VxX2xlbihuY29sKGFkLmNvcnJfc2NhbGUubGlzdFtbaV1dKSkpewogICAgIyBmb3IgZWFjaCB2YXJpYWJsZQogICAgYWQuZml0LnJlcy50cnQgPC0gbG0oYWQuY29ycl9zY2FsZS5saXN0W1tpXV1bLGNdIH4gYWQudHJ0KQogICAgYWQucl92YWx1ZXNbYywxXSA8LSBzdW1tYXJ5KGFkLmZpdC5yZXMudHJ0KSRyLnNxdWFyZWQKICAgIGFkLmZpdC5yZXMuYmF0Y2ggPC0gbG0oYWQuY29ycl9zY2FsZS5saXN0W1tpXV1bLGNdIH4gYWQuYmF0Y2gpCiAgICBhZC5yX3ZhbHVlc1tjLDJdIDwtIHN1bW1hcnkoYWQuZml0LnJlcy5iYXRjaCkkci5zcXVhcmVkCiAgfQogIGFkLnJfdmFsdWVzLmxpc3RbW2ldXSA8LSBhZC5yX3ZhbHVlcwp9Cm5hbWVzKGFkLnJfdmFsdWVzLmxpc3QpIDwtIG5hbWVzKGFkLmNvcnJfc2NhbGUubGlzdCkKaGVhZChhZC5yX3ZhbHVlcy5saXN0JGBCZWZvcmUgY29ycmVjdGlvbmApCgojIGdlbmVyYXRlIGJveHBsb3RzIGZvciBlYWNoIGRhdGFzZXQKYWQuYm94cC5saXN0IDwtIGxpc3QoKQpmb3IoaSBpbiBzZXFfbGVuKGxlbmd0aChhZC5yX3ZhbHVlcy5saXN0KSkpewogIGFkLmJveHAubGlzdFtbaV1dIDwtIAogICAgZGF0YS5mcmFtZShyMiA9IGMoYWQucl92YWx1ZXMubGlzdFtbaV1dWyAsJ3RydCddLAogICAgICAgICAgICAgICAgICAgICAgYWQucl92YWx1ZXMubGlzdFtbaV1dWyAsJ2JhdGNoJ10pLCAKICAgICAgICAgICAgICAgRWZmZWN0cyA9IGFzLmZhY3RvcihyZXAoYygnVHJlYXRtZW50JywnQmF0Y2gnKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVhY2ggPSAyMzEpKSkKfQpuYW1lcyhhZC5ib3hwLmxpc3QpIDwtIG5hbWVzKGFkLnJfdmFsdWVzLmxpc3QpCmhlYWQoYWQuYm94cC5saXN0JGBCZWZvcmUgY29ycmVjdGlvbmApCgphZC5yMi5ib3hwIDwtIHJiaW5kKGFkLmJveHAubGlzdCRgQmVmb3JlIGNvcnJlY3Rpb25gLAogICAgICAgICAgICAgICAgICAgIGFkLmJveHAubGlzdCRDb21CYXQsCiAgICAgICAgICAgICAgICAgICAgYWQuYm94cC5saXN0JGBQTFNEQS1iYXRjaGAsCiAgICAgICAgICAgICAgICAgICAgYWQuYm94cC5saXN0JFJVVklJSSkKCmFkLnIyLmJveHAkbWV0aG9kcyA8LSByZXAoYygnQmVmb3JlIGNvcnJlY3Rpb24nLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICdDb21CYXQnLCdQTFNEQS1iYXRjaCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAnUlVWSUlJJyksIGVhY2ggPSA0NjIpCgphZC5yMi5ib3hwJG1ldGhvZHMgPC0gZmFjdG9yKGFkLnIyLmJveHAkbWV0aG9kcywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gdW5pcXVlKGFkLnIyLmJveHAkbWV0aG9kcykpCmhlYWQoYWQucjIuYm94cCkKCmdncGxvdChhZC5yMi5ib3hwLCBhZXMoeCA9IEVmZmVjdHMsIHkgPSByMiwgZmlsbCA9IEVmZmVjdHMpKSArCiAgZ2VvbV9ib3hwbG90KGFscGhhID0gMC44MCkgKwogIHRoZW1lX2J3KCkgKyAKICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxOCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDYwLCBoanVzdCA9IDEsIHNpemUgPSAxOCksCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE4KSwKICAgICAgICBwYW5lbC5ncmlkLm1pbm9yLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpICsgZmFjZXRfZ3JpZCggfiBtZXRob2RzKSArIAogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1wYl9jb2xvcihjKDEyLDE0KSkpIAoKYGBgCkFzIHNob3duIGluIHRoZSBwbG90cywgdGhlIGRhdGEgY29ycmVjdGVkIHVzaW5nIENvbUJhdCBzdGlsbCBjb250YWluZWQgYSBmZXcgdmFyaWFibGVzIHdpdGggYSBsYXJnZSBwcm9wb3J0aW9uIG9mIGJhdGNoIHZhcmlhbmNlLiBJbiB0aGUgY2FzZSBvZiBSVVZJSUksIHRoZSBjb3JyZWN0ZWQgZGF0YSBleGhpYml0ZWQgYSBncmVhdGVyIHByb3BvcnRpb24gb2YgYmF0Y2ggdmFyaWFuY2UgdGhhbiB0cmVhdG1lbnQgdmFyaWFuY2UuCgoKIyMjIEFsaWdubWVudCBzY29yZXMKCkJlZm9yZSBhcHBseWluZyBgYWxpZ25tZW50X3Njb3JlKClgIGZ1bmN0aW9uIGZyb20gKlBMU0RBYmF0Y2gqLCB3ZSBuZWVkIHRvIHNwZWNpZnkgdGhlIHByb3BvcnRpb24gb2YgZGF0YSB2YXJpYW5jZSB0byBleHBsYWluIChgdmFyYCksIHRoZSBudW1iZXIgb2YgbmVhcmVzdCBuZWlnaGJvdXJzIChga2ApIGFuZCB0aGUgbnVtYmVyIG9mIFBDcyB0byBjYWxjdWxhdGUgKGBuY29tcGApIGZvciB0aGUgaW50ZXJuYWwgUENBLiBXZSBjYW4gdGhlbiB1c2UgYGdncGxvdCgpYCBmdW5jdGlvbiBmcm9tICpnZ3Bsb3QyKiB0byB2aXN1YWxpc2UgdGhlIHJlc3VsdHMuCgoKYGBge3IgQURhbGlnbm1lbnQsIGZpZy5hbGlnbiA9ICdjZW50ZXInLCBmaWcuY2FwID0gJ0NvbXBhcmlzb24gb2YgYWxpZ25tZW50IHNjb3JlcyBiZWZvcmUgYW5kIGFmdGVyIGJhdGNoIGVmZmVjdCBjb3JyZWN0aW9uIHVzaW5nIGRpZmZlcmVudCBtZXRob2RzIGZvciB0aGUgQUQgZGF0YS4nfQojIGNhbGN1bGF0ZSB0aGUgYWxpZ25tZW50IHNjb3JlcwphZC5zY29yZXMgPC0gYygpCm5hbWVzKGFkLmJhdGNoKSA8LSByb3duYW1lcyhhZC5jbHIpCmZvcihpIGluIHNlcV9sZW4obGVuZ3RoKGFkLmNvcnJlY3RlZC5saXN0KSkpewogIHJlcyA8LSBhbGlnbm1lbnRfc2NvcmUoZGF0YSA9IGFkLmNvcnJlY3RlZC5saXN0W1tpXV0sIAogICAgICAgICAgICAgICAgICAgICAgICAgYmF0Y2ggPSBhZC5iYXRjaCwgCiAgICAgICAgICAgICAgICAgICAgICAgICB2YXIgPSAwLjk1LCAKICAgICAgICAgICAgICAgICAgICAgICAgIGsgPSA4LCAKICAgICAgICAgICAgICAgICAgICAgICAgIG5jb21wID0gNTApCiAgYWQuc2NvcmVzIDwtIGMoYWQuc2NvcmVzLCByZXMpCn0KaGVhZChhZC5zY29yZXMpCgojIHJlYXJyYW5nZSB0aGUgZGF0YSBmb3IgZ2dwbG90CmFkLnNjb3Jlcy5kZiA8LSBkYXRhLmZyYW1lKHNjb3JlcyA9IGFkLnNjb3JlcywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZHMgPSBuYW1lcyhhZC5jb3JyZWN0ZWQubGlzdCkpCgphZC5zY29yZXMuZGYkbWV0aG9kcyA8LSBmYWN0b3IoYWQuc2NvcmVzLmRmJG1ldGhvZHMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gcmV2KG5hbWVzKGFkLmNvcnJlY3RlZC5saXN0KSkpCgpoZWFkKGFkLnNjb3Jlcy5kZikKCmdncGxvdCgpICsgZ2VvbV9jb2woYWVzKHggPSBhZC5zY29yZXMuZGYkbWV0aG9kcywgCiAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBhZC5zY29yZXMuZGYkc2NvcmVzKSkgKyAKICBnZW9tX3RleHQoYWVzKHggPSBhZC5zY29yZXMuZGYkbWV0aG9kcywgCiAgICAgICAgICAgICAgICB5ID0gYWQuc2NvcmVzLmRmJHNjb3Jlcy8yLCAKICAgICAgICAgICAgICAgIGxhYmVsID0gcm91bmQoYWQuc2NvcmVzLmRmJHNjb3JlcywgMykpLCAKICAgICAgICAgICAgc2l6ZSA9IDMsIGNvbCA9ICd3aGl0ZScpICsgCiAgY29vcmRfZmxpcCgpICsgdGhlbWVfYncoKSArIHlsYWIoJ0FsaWdubWVudCBTY29yZXMnKSArIAogIHhsYWIoJycpICsgeWxpbSgwLDAuODUpCmBgYAoKVGhlIGFsaWdubWVudCBzY29yZXMgY29tcGxlbWVudCB0aGUgUENBIHJlc3VsdHMsIGVzcGVjaWFsbHkgd2hlbiBiYXRjaCBlZmZlY3QgcmVtb3ZhbCBpcyBkaWZmaWN1bHQgdG8gZXZhbHVhdGUgZnJvbSBQQ0Egc2FtcGxlIHBsb3RzIGFsb25lLiBGb3IgZXhhbXBsZSwgaW4gdGhlIHByZXZpb3VzIFBDQSBwbG90cyAoc2VlIHNlY3Rpb24gIlBDQSIpLCB3ZSBvYnNlcnZlZCB0aGF0IHNhbXBsZXMgZnJvbSBkaWZmZXJlbnQgYmF0Y2hlcyBhcHBlYXJlZCBtb3JlIGludGVybWl4ZWQgYWZ0ZXIgYmF0Y2ggZWZmZWN0IGNvcnJlY3Rpb24sIHJlZ2FyZGxlc3Mgb2YgdGhlIG1ldGhvZCB1c2VkLiBIb3dldmVyLCBjb21wYXJpbmcgdGhlIHBlcmZvcm1hbmNlIG9mIGRpZmZlcmVudCBtZXRob2RzIHJlbWFpbmVkIGNoYWxsZW5naW5nLgoKU2luY2UgYSBoaWdoZXIgYWxpZ25tZW50IHNjb3JlIGluZGljYXRlcyBiZXR0ZXIgbWl4aW5nIG9mIHNhbXBsZXMgYWNyb3NzIGJhdGNoZXMsIHRoZSBiYXIgcGxvdCBhYm92ZSBzaG93cyB0aGF0IENvbUJhdCBhY2hpZXZlZCBzdXBlcmlvciBwZXJmb3JtYW5jZSBjb21wYXJlZCB0byB0aGUgb3RoZXIgbWV0aG9kcy4gSG93ZXZlciwgdGhlICRSXjIkIGFuYWx5c2lzIHJldmVhbGVkIHRoYXQgdGhlIGRhdGEgY29ycmVjdGVkIHdpdGggQ29tQmF0IHN0aWxsIGNvbnRhaW5lZCBhIGZldyB2YXJpYWJsZXMgd2l0aCBhIGxhcmdlIHByb3BvcnRpb24gb2YgYmF0Y2ggdmFyaWFuY2UgKHNlZSBzZWN0aW9uICIkUl4yJCIpLiBUaGlzIGhpZ2hsaWdodHMgdGhlIGltcG9ydGFuY2Ugb2YgZXZhbHVhdGluZyBjb3JyZWN0aW9uIGVmZmVjdGl2ZW5lc3MgdXNpbmcgbXVsdGlwbGUgdGVjaG5pcXVlcyB0byBlbnN1cmUgYW4gdW5iaWFzZWQgYXNzZXNzbWVudC4KCkluIHRoaXMgZXhhbXBsZSwgdGhlIGxvd2VyIGFsaWdubWVudCBzY29yZSBvYnNlcnZlZCBmb3IgdGhlIGRhdGEgY29ycmVjdGVkIHVzaW5nIFBMU0RBLWJhdGNoIGNvbXBhcmVkIHRvIENvbUJhdCBtYXkgYmUgZHVlIHRvIGRpZmZlcmVuY2VzIGluIHRoZSBQQ0Egc2FtcGxlIHByb2plY3Rpb25zLiBTcGVjaWZpY2FsbHksIHRoZSBkYXRhIGNvcnJlY3RlZCB3aXRoIENvbUJhdCBleGhpYml0ZWQgZ3JlYXRlciB2YXJpYW5jZSBpbiBpdHMgUENBIHByb2plY3Rpb24sIHdoZXJlYXMgdGhlIGRhdGEgd2l0aCBQTFNEQS1iYXRjaCBzaG93ZWQgc21hbGxlciB2YXJpYW5jZS4gU21hbGxlciB2YXJpYW5jZSBpbiB0aGUgcHJvamVjdGlvbiBjYW4gbGVhZCB0byBsb3dlciBhbGlnbm1lbnQgc2NvcmVzLCBhcyBpdCBpbmNyZWFzZXMgdGhlIGxpa2VsaWhvb2QgdGhhdCBzYW1wbGVzIGZyb20gZGlmZmVyZW50IGJhdGNoZXMgYXBwZWFyIGFzIG5lYXJlc3QgbmVpZ2hib3Vycy4gTm9uZXRoZWxlc3MsIHRoZSBwUkRBIHJlc3VsdHMgKHNlZSBzZWN0aW9uICJwUkRBIikgcXVhbnRpdGF0aXZlbHkgY29uZmlybWVkIHRoYXQgUExTREEtYmF0Y2ggZWZmZWN0aXZlbHkgcmVtb3ZlZCBiYXRjaCB2YXJpYW5jZSBlbnRpcmVseSBbQHdhbmcyMDIzcGxzZGFdLgoKCiMgRGlzY3Vzc2lvbiBhbmQgY29uY2x1c2lvbnMKClRoaXMgd29ya3Nob3AgcHJlc2VudHMgYSBjb21wcmVoZW5zaXZlIGZyYW1ld29yayBmb3IgbWFuYWdpbmcgYmF0Y2ggZWZmZWN0cywgdXNpbmcgbWljcm9iaW9tZSBkYXRhIGFzIGFuIGlsbHVzdHJhdGl2ZSBleGFtcGxlLiBUaGUgaW5wdXQgZm9yIHRoaXMgd29ya2Zsb3cgaXMgcHJlcHJvY2Vzc2VkIGRhdGEuIEZvciBkaWZmZXJlbnQgdHlwZXMgb2Ygb21pY3MgZGF0YSwgdGhlIHByZXByb2Nlc3Npbmcgc3RlcHMgbWF5IHZhcnkuIAoKVG8gZGV0ZWN0IGJhdGNoIGVmZmVjdHMsIGJvdGggdmlzdWFsIHRvb2xzIChlLmcuLCBQQ0EsIGJveHBsb3RzLCBkZW5zaXR5IHBsb3RzLCBhbmQgaGVhdG1hcHMpIGFuZCBxdWFudGl0YXRpdmUgbWV0aG9kcyBzdWNoIGFzIHBSREEgY2FuIGJlIGFwcGxpZWQuIElmIGJhdGNoIGVmZmVjdHMgYXBwZWFyIG5lZ2xpZ2libGUsIGZvciBpbnN0YW5jZSwgd2hlbiBwUkRBIHNob3dzIG9ubHkgYSBtaW5pbWFsIHByb3BvcnRpb24gb2YgdmFyaWFuY2UgZXhwbGFpbmVkIGJ5IGJhdGNoIG9yIHdoZW4gUENBIGRvZXMgbm90IHJldmVhbCBjbGVhciBiYXRjaCBkcml2ZW4gY2x1c3RlcnMsIGJhdGNoIGVmZmVjdCBtYW5hZ2VtZW50IG1heSBub3QgYmUgbmVjZXNzYXJ5LiBIb3dldmVyLCB3aGVuIGJhdGNoIGVmZmVjdHMgYXJlIHN1YnN0YW50aWFsLCB0d28gc3RyYXRlZ2llcyBjYW4gYmUgY29uc2lkZXJlZDogYWNjb3VudGluZyBmb3IgYmF0Y2ggZWZmZWN0cyBkdXJpbmcgbW9kZWxpbmcsIG9yIHJlbW92aW5nIHRoZW0gZnJvbSB0aGUgZGF0YSBwcmlvciB0byBhbmFseXNpcy4KCkJvdGggc3RyYXRlZ2llcyBhc3N1bWUgYSBiYWxhbmNlZCBiYXRjaCDDlyB0cmVhdG1lbnQgZGVzaWduLiBJZiB0aGUgZGVzaWduIGlzIG5lc3RlZCwgYmF0Y2ggZWZmZWN0cyBjYW4gb25seSBiZSBhY2NvdW50ZWQgZm9yIHVzaW5nIGEgbGluZWFyIG1peGVkIG1vZGVsLiBJZiB0aGUgZGVzaWduIGlzIHVuYmFsYW5jZWQgYnV0IG5vdCBuZXN0ZWQsIGJhdGNoIGVmZmVjdHMgY2FuIGVpdGhlciBiZSBjb250cm9sbGVkIHVzaW5nIHN0YW5kYXJkIGxpbmVhciBtb2RlbHMgb3IgcmVtb3ZlZCB1c2luZyB3ZWlnaHRlZCBQTFNEQS1iYXRjaC4gCgpJbiBtb3N0IGNhc2VzLCBiYXRjaCBncm91cGluZyBpbmZvcm1hdGlvbiBpcyBhc3N1bWVkIHRvIGJlIGtub3duLiBXaGVuIGl0IGlzIG5vdCwgYmF0Y2ggZXN0aW1hdGlvbiBtZXRob2RzIHN1Y2ggYXMgUlVWNCwgUlVWSUlJIGFuZCBTVkEgY2FuIGJlIGVtcGxveWVkLiBSVVYtYmFzZWQgbWV0aG9kcyByZWx5IG9uIG5lZ2F0aXZlIGNvbnRyb2wgdmFyaWFibGVzIGFuZC9vciBzYW1wbGUgcmVwbGljYXRlcywgd2hpbGUgU1ZBIGVzdGltYXRlcyBiYXRjaCBlZmZlY3RzIGZyb20gdmFyaWFibGVzIHRoYXQgYXJlIGxlYXN0IGFmZmVjdGVkIGJ5IHRyZWF0bWVudCAoc2VlIHNlY3Rpb24gIlRvIGdvIGZ1cnRoZXIiKS4gSG93ZXZlciwgZm9yIFJVViBtZXRob2RzLCB0aGVzZSBuZWdhdGl2ZSBjb250cm9scyBhbmQgc2FtcGxlIHJlcGxpY2F0ZXMgbXVzdCBjYXB0dXJlIHRoZSBmdWxsIHNwZWN0cnVtIG9mIGJhdGNoIHZhcmlhdGlvbjsgb3RoZXJ3aXNlLCBiYXRjaCBlZmZlY3RzIG1heSBub3QgYmUgY29tcGxldGVseSBhZGRyZXNzZWQuIFdlIGVtcGhhc2lzZWQgdGhpcyBpc3N1ZSBpbiB0aGUgc2VjdGlvbiAiQXNzZXNzaW5nIGJhdGNoIGVmZmVjdCBjb3JyZWN0aW9uIC0gcFJEQSIsIHdoZXJlIGxpbWl0ZWQgcmVwbGljYXRlcyBwb3NlZCBjaGFsbGVuZ2VzIGZvciBiYXRjaCBlZmZlY3QgcmVtb3ZhbC4gTW9yZW92ZXIsIHRoZXNlIGVzdGltYXRpb24gbWV0aG9kcyBhc3N1bWUgdGhhdCBiYXRjaCBlZmZlY3RzIGFyZSBpbmRlcGVuZGVudCBvZiB0cmVhdG1lbnQuIFdoZW4gYmF0Y2ggYW5kIHRyZWF0bWVudCBhcmUgY29ycmVsYXRlZCwgdGhlIGJhdGNoIGVmZmVjdCBjYW5ub3QgYmUgYWNjdXJhdGVseSBlc3RpbWF0ZWQgYW5kIG1heSBsZWFkIHRvIHNwdXJpb3VzIGFzc29jaWF0aW9ucy4KCkFkZGl0aW9uYWxseSwgbW9zdCBtZXRob2RzIGZvciBiYXRjaCBlZmZlY3QgbWFuYWdlbWVudCBhc3N1bWUgc3lzdGVtYXRpYyBiYXRjaCBlZmZlY3RzIGFjcm9zcyB2YXJpYWJsZXMsIGFuZCBzb21lIG1vZGVscyBhcmUgaGlnaGx5IGluZmx1ZW5jZWQgYnkgdGhpcyBhc3N1bXB0aW9uIChlLmcuLCBTVkEsIENvbUJhdCBhbmQgUlVWIG1ldGhvZHMpLiBUaGVyZWZvcmUsIHdlIHJlY29tbWVuZCB2YWxpZGF0aW5nIHRoZSBtb2RlbCBiZWZvcmUgYXBwbHlpbmcgaXQuCgpUaGUgbmV4dCBjcml0aWNhbCBzdGVwIGlzIHRvIGV2YWx1YXRlIHRoZSBlZmZlY3RpdmVuZXNzIG9mIGJhdGNoIGVmZmVjdCBtYW5hZ2VtZW50LiBXaGlsZSBzdWNoIGV2YWx1YXRpb24gaXMgb2Z0ZW4gaW1wbGljaXQgaW4gbWV0aG9kcyB0aGF0IGFjY291bnQgZm9yIGJhdGNoIGVmZmVjdHMsIGl0IGlzIGVzc2VudGlhbCBmb3IgbWV0aG9kcyB0aGF0IHJlbW92ZSB0aGVtLiBUaGlzIGNhbiBiZSBkb25lIGJ5IGNvbXBhcmluZyB0aGUgZGF0YSBiZWZvcmUgYW5kIGFmdGVyIGNvcnJlY3Rpb24gdXNpbmcgdGhlIHNhbWUgdG9vbHMgZW1wbG95ZWQgZm9yIGJhdGNoIGVmZmVjdCBkZXRlY3Rpb24uIEFkZGl0aW9uYWxseSwgJFJeMiQgdmFsdWVzIGNhbiBiZSBjYWxjdWxhdGVkIGZvciBlYWNoIG1pY3JvYmlhbCB2YXJpYWJsZSB0byBxdWFudGlmeSB0aGUgdmFyaWFuY2UgZXhwbGFpbmVkIGJ5IGJhdGNoIGFuZCB0cmVhdG1lbnQsIGFuZCBhbGlnbm1lbnQgc2NvcmVzIGNhbiBiZSB1c2VkIHRvIGFzc2VzcyBob3cgd2VsbCBzYW1wbGVzIGZyb20gZGlmZmVyZW50IGJhdGNoZXMgYXJlIG1peGVkLiBIb3dldmVyLCBpbmRpdmlkdWFsIGV2YWx1YXRpb24gdG9vbHMgbWF5IGhhdmUgbGltaXRhdGlvbnMuIEZvciBleGFtcGxlLCBQQ0EgcmVsaWVzIG9uIHZpc3VhbCBpbnRlcnByZXRhdGlvbiwgd2hpbGUgYWxpZ25tZW50IHNjb3JlcyBmb2N1cyBzb2xlbHkgb24gZGlzdGFuY2VzIGluIHRoZSBQQ0EgcHJvamVjdGlvbiBzcGFjZS4gVGhlcmVmb3JlLCBhIHJvYnVzdCBjb25jbHVzaW9uIHNob3VsZCBiZSBiYXNlZCBvbiBtdWx0aXBsZSBjb21wbGVtZW50YXJ5IGV2YWx1YXRpb24gbWV0aG9kcy4KCk9uY2UgYmF0Y2ggZWZmZWN0cyBoYXZlIGJlZW4gYWRlcXVhdGVseSBhZGRyZXNzZWQsIGRvd25zdHJlYW0gYW5hbHlzZXMsIHN1Y2ggYXMgbXVsdGl2YXJpYXRlIGRpc2NyaW1pbmFudCBhbmFseXNpcyBvciB1bml2YXJpYXRlIGRpZmZlcmVudGlhbCBhbmFseXNpcywgY2FuIGJlIHBlcmZvcm1lZC4gQW1vbmcgdGhlc2UsIG11bHRpdmFyaWF0ZSBtZXRob2RzIGFyZSBvZnRlbiBtb3JlIGFwcHJvcHJpYXRlIGZvciBtaWNyb2Jpb21lIGRhdGEsIGdpdmVuIHRoZSBuYXR1cmFsIGNvcnJlbGF0aW9ucyBhbW9uZyBtaWNyb2JpYWwgdmFyaWFibGVzIGFyaXNpbmcgZnJvbSBiaW9sb2dpY2FsIGludGVyYWN0aW9ucy4KCgojIFRvIGdvIGZ1cnRoZXIKCiMjIEFjY291bnRpbmcgZm9yIGJhdGNoIGVmZmVjdHMKCiMjIyBNZXRob2RzIGRlc2lnbmVkIGZvciBtaWNyb2Jpb21lIGRhdGEKCiMjIyMgWmVyby1pbmZsYXRlZCBHYXVzc2lhbiBtaXh0dXJlIG1vZGVsIAoKVG8gdXNlIHRoZSBaSUcgbW9kZWwsIHdlIGZpcnN0IG5lZWQgdG8gY3JlYXRlIGFuIGBNUmV4cGVyaW1lbnRgIG9iamVjdCBieSBhcHBseWluZyBgbmV3TVJleHBlcmltZW50KClgIChmcm9tICptZXRhZ2Vub21lU2VxKiBwYWNrYWdlKSB0byBtaWNyb2Jpb21lIGNvdW50cyBhbmQgYW5ub3RhdGVkIGRhdGEgZnJhbWVzIGNvbnRhaW5pbmcgbWV0YWRhdGEgYW5kIHRheG9ub21pYyBpbmZvcm1hdGlvbiBnZW5lcmF0ZWQgdXNpbmcgYEFubm90YXRlZERhdGFGcmFtZSgpYCBmcm9tICpCaW9iYXNlKiBwYWNrYWdlLgoKYGBge3J9CiMgQ3JlYXRpbmcgYSBNUmV4cGVyaW1lbnQgb2JqZWN0IChtYWtlIHN1cmUgbm8gTkEgaW4gbWV0YWRhdGEpCkFELnBoZW5vdHlwZURhdGEgPSBBbm5vdGF0ZWREYXRhRnJhbWUoZGF0YSA9IGFzLmRhdGEuZnJhbWUoYWQubWV0YWRhdGEpKQpBRC50YXhhRGF0YSA9IEFubm90YXRlZERhdGFGcmFtZShkYXRhID0gYXMuZGF0YS5mcmFtZShjb2xEYXRhKEFEX2RhdGEkRnVsbERhdGEpKSkKQUQub2JqID0gbmV3TVJleHBlcmltZW50KGNvdW50cyA9IHQoYWQuY291bnQpLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHBoZW5vRGF0YSA9IEFELnBoZW5vdHlwZURhdGEsIAogICAgICAgICAgICAgICAgICAgICAgICAgZmVhdHVyZURhdGEgPSBBRC50YXhhRGF0YSkKQUQub2JqCmBgYAoKVGhlICoqQUQgY291bnQgZGF0YSoqIGFyZSB0aGVuIGZpbHRlcmVkIHdpdGggYGZpbHRlckRhdGEoKWAgZnVuY3Rpb24gKGZyb20gKm1ldGFnZW5vbWVTZXEqIHBhY2thZ2UpLiBXZSBjYW4gdXNlIGBNUmNvdW50cygpYCB0byBleHRyYWN0IHRoZSBjb3VudCBkYXRhIGZyb20gdGhlIGBNUmV4cGVyaW1lbnRgIG9iamVjdC4KCmBgYHtyfQojIGZpbHRlcmluZyBkYXRhIHRvIG1haW50YWluIGEgdGhyZXNob2xkIG9mIG1pbmltdW0gZGVwdGggb3IgT1RVIHByZXNlbmNlCmRpbShNUmNvdW50cyhBRC5vYmopKQpBRC5vYmogPSBmaWx0ZXJEYXRhKG9iaiA9IEFELm9iaiwgcHJlc2VudCA9IDIwLCBkZXB0aCA9IDUpCmRpbShNUmNvdW50cyhBRC5vYmopKQpgYGAKCkFmdGVyIGZpbHRlcmluZywgdGhlICoqQUQgY291bnQgZGF0YSoqIHdlcmUgcmVkdWNlZCB0byAyODkgT1RVcyBhbmQgNzUgc2FtcGxlcy4KCldlIHdpbGwgdGhlbiBjYWxjdWxhdGUgdGhlIHBlcmNlbnRpbGUgZm9yIENTUyBub3JtYWxpc2F0aW9uIHdpdGggYGN1bU5vcm1TdGF0RmFzdCgpYCBmdW5jdGlvbiAoZnJvbSAqbWV0YWdlbm9tZVNlcSogcGFja2FnZSkuIFRoZSBDU1Mgbm9ybWFsaXNhdGlvbiBjYW4gYmUgYXBwbGllZCB3aXRoIGBjdW1Ob3JtKClgIGFuZCB0aGUgbm9ybWFsaXNlZCBkYXRhIGNhbiBiZSBleHBvcnRlZCB1c2luZyBgTVJjb3VudHMoKWAgd2l0aCBgbm9ybSA9IFRSVUVgLiBUaGUgbm9ybWFsaXNhdGlvbiBzY2FsaW5nIGZhY3RvcnMgZm9yIGVhY2ggc2FtcGxlLCB3aGljaCBhcmUgdGhlIHN1bSBvZiBjb3VudHMgdXAgdG8gdGhlIGNhbGN1bGF0ZWQgcGVyY2VudGlsZSwgY2FuIGJlIGFjY2Vzc2VkIHRocm91Z2ggYG5vcm1GYWN0b3JzKClgLiBXZSBjYW4gY2FsY3VsYXRlIHRoZSBsb2cgdHJhbnNmb3JtZWQgc2NhbGluZyBmYWN0b3JzIGJ5IGRpdmluZyB0aGVtIHdpdGggdGhlaXIgbWVkaWFuLCB3aGljaCBhcmUgYmV0dGVyIHRoYW4gdGhlIGRlZmF1bHQgc2NhbGluZyBmYWN0b3JzIHRoYXQgYXJlIGRpdmlkZWQgYnkgMTAwMCAoYGxvZzIobm9ybUZhY3RvcnMob2JqKS8xMDAwICsgMSlgKS4KCgpgYGB7cn0KIyBjYWxjdWxhdGUgdGhlIHBlcmNlbnRpbGUgZm9yIENTUyBub3JtYWxpc2F0aW9uCkFELnBjdGwgPSBjdW1Ob3JtU3RhdEZhc3Qob2JqID0gQUQub2JqKQojIENTUyBub3JtYWxpc2F0aW9uCkFELm9iaiA8LSBjdW1Ob3JtKG9iaiA9IEFELm9iaiwgcCA9IEFELnBjdGwpCiMgZXhwb3J0IG5vcm1hbGlzZWQgZGF0YQpBRC5ub3JtLmRhdGEgPC0gTVJjb3VudHMob2JqID0gQUQub2JqLCBub3JtID0gVFJVRSkKCiMgbm9ybWFsaXNhdGlvbiBzY2FsaW5nIGZhY3RvcnMgZm9yIGVhY2ggc2FtcGxlIApBRC5ub3JtRmFjdG9yID0gbm9ybUZhY3RvcnMob2JqZWN0ID0gQUQub2JqKQpBRC5ub3JtRmFjdG9yID0gbG9nMihBRC5ub3JtRmFjdG9yL21lZGlhbihBRC5ub3JtRmFjdG9yKSArIDEpCmBgYAoKQWZ0ZXIgdGhhdCwgbGV0J3MgY3JlYXRlIGEgZGVzaWduIG1hdHJpeCB3aXRoIHRyZWF0bWVudCB2YXJpYWJsZSAoYHBoZW5vbF9jb25jYCksIGJhdGNoIHZhcmlhYmxlIChgc2VxX3J1bmApIGFuZCB0aGUgbG9nIHRyYW5zZm9ybWVkIHNjYWxpbmcgZmFjdG9ycyB1c2luZyBgbW9kZWwubWF0cml4KClgLCBhbmQgdGhlbiBhcHBseSB0aGUgWklHIG1vZGVsIGJ5IGBmaXRaaWcoKWAgZnVuY3Rpb24uIFdlIHNob3VsZCBzZXQgYHVzZUNTU29mZnNldCA9IEZBTFNFYCB0byBhdm9pZCB1c2luZyB0aGUgZGVmYXVsdCBzY2FsaW5nIGZhY3RvcnMgYXMgd2UgaGF2ZSBhbHJlYWR5IGluY2x1ZGVkIG91ciBjdXN0b21pc2VkIHNjYWxpbmcgZmFjdG9yIChgQUQubm9ybUZhY3RvcmApIGluIHRoZSBkZXNpZ24gbWF0cml4LgoKCmBgYHtyfQojIHRyZWF0bWVudCB2YXJpYWJsZQpwaGVub2xfY29uYyA9IHBEYXRhKG9iamVjdCA9IEFELm9iaikkaW5pdGlhbF9waGVub2xfY29uY2VudHJhdGlvbi5yZWdyb3VwCiMgYmF0Y2ggdmFyaWFibGUKc2VxX3J1biA9IHBEYXRhKG9iamVjdCA9IEFELm9iaikkc2VxdWVuY2luZ19ydW5fZGF0ZQoKIyBidWlsZCBhIGRlc2lnbiBtYXRyaXgKQUQubW9kLmZ1bGwgPSBtb2RlbC5tYXRyaXgofiBwaGVub2xfY29uYyArIHNlcV9ydW4gKyBBRC5ub3JtRmFjdG9yKQoKIyBzZXR0aW5ncyBmb3IgdGhlIGZpdFppZygpIGZ1bmN0aW9uCkFELnNldHRpbmdzIDwtIHppZ0NvbnRyb2wobWF4aXQgPSAxMCwgdmVyYm9zZSA9IFRSVUUpCgojIGFwcGx5IHRoZSBaSUcgbW9kZWwKQURmaXQgPC0gZml0WmlnKG9iaiA9IEFELm9iaiwgbW9kID0gQUQubW9kLmZ1bGwsIAogICAgICAgICAgICAgICAgdXNlQ1NTb2Zmc2V0ID0gRkFMU0UsIGNvbnRyb2wgPSBBRC5zZXR0aW5ncykKCmBgYAoKVGhlIE9UVXMgd2l0aCB0aGUgdG9wIDUwIHNtYWxsZXN0IHAgdmFsdWVzIGNhbiBiZSBleHRyYWN0ZWQgdXNpbmcgYE1SY29lZnMoKWAuIExldCdzIHNldCBgZWZmID0gMC41YCwgc28gb25seSB0aGUgT1RVcyB3aXRoIGF0IGxlYXN0ICIwLjUiIHF1YW50aWxlICg1MCUpIG51bWJlciBvZiBlZmZlY3RpdmUgc2FtcGxlcyAocG9zaXRpdmUgc2FtcGxlcyArIGVzdGltYXRlZCB1bmRlcnNhbXBsaW5nIHplcm9lcykgYXJlIGV4dHJhY3RlZC4KCgpgYGB7cn0KQURjb2VmcyA8LSBNUmNvZWZzKEFEZml0LCBjb2VmID0gMiwgZ3JvdXAgPSAzLCBudW1iZXIgPSA1MCwgZWZmID0gMC41KQpoZWFkKEFEY29lZnMpCmBgYAoKIyMjIE1ldGhvZHMgYWRhcHRlZCBmb3IgbWljcm9iaW9tZSBkYXRhCgojIyMjIFNWQQoKU1ZBIG9ubHkgYWNjb3VudHMgZm9yIHVua25vd24gYmF0Y2ggZWZmZWN0cy4gVGhlcmVmb3JlLCB3ZSBhc3N1bWUgdGhhdCB0aGUgYmF0Y2ggZ3JvdXBpbmcgaW5mb3JtYXRpb24gaW4gdGhlICoqQUQgZGF0YSoqIGlzIHVua25vd24uIFdlIGZpcnN0IG5lZWQgdG8gYnVpbGQgdHdvIGRlc2lnbiBtYXRyaWNlcyB3aXRoIChgYWQubW9kYCkgYW5kIHdpdGhvdXQgKGBhZC5tb2QwYCkgdHJlYXRtZW50IGdyb3VwaW5nIGluZm9ybWF0aW9uIGdlbmVyYXRlZCB3aXRoIGBtb2RlbC5tYXRyaXgoKWAgZnVuY3Rpb24gZnJvbSAqc3RhdHMqLiBXZSB0aGVuIG5lZWQgdG8gdXNlIGBudW0uc3YoKWAgZnJvbSAqc3ZhKiBwYWNrYWdlIHRvIGRldGVybWluZSB0aGUgbnVtYmVyIG9mIGJhdGNoIHZhcmlhYmxlcyBgbi5zdmAgdGhhdCBpcyB1c2VkIHRvIGVzdGltYXRlIGJhdGNoIGVmZmVjdHMgaW4gZnVuY3Rpb24gYHN2YSgpYC4KCmBgYHtyfQojIGVzdGltYXRlIGJhdGNoIGVmZmVjdHMKYWQubW9kIDwtIG1vZGVsLm1hdHJpeCggfiBhZC50cnQpCmFkLm1vZDAgPC0gbW9kZWwubWF0cml4KCB+IDEsIGRhdGEgPSBhZC50cnQpCmFkLnN2YS5uIDwtIG51bS5zdihkYXQgPSB0KGFkLmNsciksIG1vZCA9IGFkLm1vZCwgbWV0aG9kID0gJ2xlZWsnKQphZC5zdmEgPC0gc3ZhKHQoYWQuY2xyKSwgYWQubW9kLCBhZC5tb2QwLCBuLnN2ID0gYWQuc3ZhLm4pCmBgYAoKVGhlIGVzdGltYXRlZCBiYXRjaCBlZmZlY3RzIGFyZSB0aGVuIGlucHV0IGludG8gYGYucHZhbHVlKClgIHRvIGNhbGN1bGF0ZSB0aGUgUC12YWx1ZXMgb2YgdHJlYXRtZW50IGVmZmVjdHMuIFRoZSBlc3RpbWF0ZWQgYmF0Y2ggZWZmZWN0cyBpbiBTVkEgYXJlIGFzc3VtZWQgdG8gYmUgaW5kZXBlbmRlbnQgb2YgdGhlIHRyZWF0bWVudCBlZmZlY3RzLiBIb3dldmVyLCBTVkEgY2FuIHRvbGVyYXRlIHNvbWUgc29ydCBvZiBjb3JyZWxhdGlvbiBiZXR3ZWVuIGJhdGNoIGFuZCB0cmVhdG1lbnQgZWZmZWN0cywgYnV0IG9ubHkgdG8gYSBsaW1pdGVkIGV4dGVudCBbQHdhbmcyMDIwbWFuYWdpbmddLgoKYGBge3J9CiMgaW5jbHVkZSBlc3RpbWF0ZWQgYmF0Y2ggZWZmZWN0cyBpbiB0aGUgbGluZWFyIG1vZGVsCmFkLm1vZC5iYXRjaCA8LSBjYmluZChhZC5tb2QsIGFkLnN2YSRzdikKYWQubW9kMC5iYXRjaCA8LSBjYmluZChhZC5tb2QwLCBhZC5zdmEkc3YpCmFkLnN2YS5wIDwtIGYucHZhbHVlKHQoYWQuY2xyKSwgYWQubW9kLmJhdGNoLCBhZC5tb2QwLmJhdGNoKQphZC5zdmEucC5hZGogPC0gcC5hZGp1c3QoYWQuc3ZhLnAsIG1ldGhvZCA9ICdmZHInKQpgYGAKCiMjIENvcnJlY3RpbmcgZm9yIGJhdGNoIGVmZmVjdHMKCiMjIyByZW1vdmVCYXRjaEVmZmVjdAoKQmVmb3JlIGFwcGx5aW5nIHJlbW92ZUJhdGNoRWZmZWN0LCB3ZSBuZWVkIHRvIHByZXBhcmUgYSBkZXNpZ24gbWF0cml4IChgZGVzaWduYCkgdGhhdCBpbmNsdWRlcyB0aGUgdHJlYXRtZW50IGdyb3VwaW5nIGluZm9ybWF0aW9uLCB3aGljaCB3aWxsIGJlIHByZXNlcnZlZCBkdXJpbmcgYmF0Y2ggZWZmZWN0IGNvcnJlY3Rpb24uIFRoaXMgZGVzaWduIG1hdHJpeCBjYW4gYmUgZ2VuZXJhdGVkIHVzaW5nIGBtb2RlbC5tYXRyaXgoKWAgZnVuY3Rpb24gZnJvbSAqc3RhdHMqLiAKClRoZW4gd2UgY2FuIHVzZSBgcmVtb3ZlQmF0Y2hFZmZlY3QoKWAgZnVuY3Rpb24gKCpsaW1tYSogcGFja2FnZSkgd2l0aCBpbnB1dCBiYXRjaCBncm91cGluZyBpbmZvcm1hdGlvbiAoYGJhdGNoYCkgYW5kIHRyZWF0bWVudCBkZXNpZ24gbWF0cml4IChgZGVzaWduYCkgdG8gY2FsY3VsYXRlIGJhdGNoIGVmZmVjdCBjb3JyZWN0ZWQgZGF0YSBgYWQuckJFYC4KCgpgYGB7cn0KYWQubW9kIDwtIG1vZGVsLm1hdHJpeCggfiBhZC50cnQpCmFkLnJCRSA8LSB0KHJlbW92ZUJhdGNoRWZmZWN0KHQoYWQuY2xyKSwgYmF0Y2ggPSBhZC5iYXRjaCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlc2lnbiA9IGFkLm1vZCkpCmBgYAoKIyMjIHNQTFNEQS1iYXRjaAoKVG8gYXBwbHkgc1BMU0RBLWJhdGNoLCB3ZSB3aWxsIHVzZSB0aGUgc2FtZSBmdW5jdGlvbiBgUExTREFfYmF0Y2goKWAsIGJ1dCB3ZSBuZWVkIHRvIHNwZWNpZnkgdGhlIG51bWJlciBvZiB2YXJpYWJsZXMgdG8gc2VsZWN0IG9uIGVhY2ggY29tcG9uZW50ICh1c3VhbGx5IG9ubHkgdHJlYXRtZW50LXJlbGF0ZWQgY29tcG9uZW50cyBga2VlcFgudHJ0YCkuIFRvIGRldGVybWluZSB0aGUgb3B0aW1hbCBudW1iZXIgb2YgdmFyaWFibGVzIHRvIHNlbGVjdCwgd2Ugd2lsbCB1c2UgYHR1bmUuc3Bsc2RhKClgIGZ1bmN0aW9uIGZyb20gKm1peE9taWNzKiBwYWNrYWdlIFtAcm9oYXJ0MjAxN21peG9taWNzXSB3aXRoIGFsbCBwb3NzaWJsZSBudW1iZXJzIG9mIHZhcmlhYmxlcyB0byBzZWxlY3QgZm9yIGVhY2ggY29tcG9uZW50IChgdGVzdC5rZWVwWGApLgoKCmBgYHtyLCBldmFsID0gRn0KIyBlc3RpbWF0ZSB0aGUgbnVtYmVyIG9mIHZhcmlhYmxlcyB0byBzZWxlY3QgcGVyIHRyZWF0bWVudCBjb21wb25lbnQKc2V0LnNlZWQoNzc3KQphZC50ZXN0LmtlZXBYID0gYyhzZXEoMSwgMTAsIDEpLCBzZXEoMjAsIDEwMCwgMTApLCAKICAgICAgICAgICAgICAgICAgc2VxKDE1MCwgMjMxLCA1MCksIDIzMSkKYWQudHJ0LnR1bmUudiA8LSB0dW5lLnNwbHNkYShYID0gYWQuY2xyLCBZID0gYWQudHJ0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuY29tcCA9IDEsIHRlc3Qua2VlcFggPSBhZC50ZXN0LmtlZXBYLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWxpZGF0aW9uID0gJ01mb2xkJywgZm9sZHMgPSA0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBucmVwZWF0ID0gNTApCmFkLnRydC50dW5lLnYkY2hvaWNlLmtlZXBYICMxMDAKCmBgYAoKSGVyZSwgdGhlIG9wdGltYWwgbnVtYmVyIG9mIHZhcmlhYmxlcyB0byBzZWxlY3QgZm9yIHRoZSB0cmVhdG1lbnQgY29tcG9uZW50IHdhcyAxMDAuIFNpbmNlIHdlIGhhdmUgYWRqdXN0ZWQgdGhlIGFtb3VudCBvZiB0cmVhdG1lbnQgdmFyaWF0aW9uIHRvIHByZXNlcnZlLCB3ZSBuZWVkIHRvIHJlLWVzdGltYXRlIHRoZSBvcHRpbWFsIG51bWJlciBvZiBjb21wb25lbnRzIHJlbGF0ZWQgdG8gYmF0Y2ggZWZmZWN0cyB1c2luZyB0aGUgc2FtZSBjcml0ZXJpb24gbWVudGlvbmVkIGluIHNlY3Rpb24gIlBMU0RBLWJhdGNoIi4KCmBgYHtyfQojIGVzdGltYXRlIHRoZSBudW1iZXIgb2YgYmF0Y2ggY29tcG9uZW50cwphZC5iYXRjaC50dW5lIDwtIFBMU0RBX2JhdGNoKFggPSBhZC5jbHIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFkudHJ0ID0gYWQudHJ0LCBZLmJhdCA9IGFkLmJhdGNoLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5jb21wLnRydCA9IDEsIGtlZXBYLnRydCA9IDEwMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuY29tcC5iYXQgPSAxMCkKYWQuYmF0Y2gudHVuZSRleHBsYWluZWRfdmFyaWFuY2UuYmF0ICM0CnN1bShhZC5iYXRjaC50dW5lJGV4cGxhaW5lZF92YXJpYW5jZS5iYXQkWVtzZXFfbGVuKDQpXSkKYGBgCgpBY2NvcmRpbmcgdG8gdGhlIHJlc3VsdCwgd2UgbmVlZGVkIDQgYmF0Y2gtcmVsYXRlZCBjb21wb25lbnRzIHRvIHJlbW92ZSBiYXRjaCB2YXJpYW5jZSBmcm9tIHRoZSBkYXRhIHdpdGggZnVuY3Rpb24gYFBMU0RBX2JhdGNoKClgLgoKYGBge3J9CmFkLnNQTFNEQV9iYXRjaC5yZXMgPC0gUExTREFfYmF0Y2goWCA9IGFkLmNsciwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgWS50cnQgPSBhZC50cnQsIFkuYmF0ID0gYWQuYmF0Y2gsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmNvbXAudHJ0ID0gMSwga2VlcFgudHJ0ID0gMTAwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5jb21wLmJhdCA9IDQpCmFkLnNQTFNEQV9iYXRjaCA8LSBhZC5zUExTREFfYmF0Y2gucmVzJFgubm9iYXRjaApgYGAKCk5vdGU6IGZvciB1bmJhbGFuY2VkIGJhdGNoIHggdHJlYXRtZW50IGRlc2lnbiAod2l0aCB0aGUgZXhjZXB0aW9uIG9mIHRoZSBuZXN0ZWQgZGVzaWduKSwgd2UgY2FuIHNwZWNpZnkgYGJhbGFuY2UgPSBGQUxTRWAgaW4gYFBMU0RBX2JhdGNoKClgIGZ1bmN0aW9uIHRvIGFwcGx5IHdlaWdodGVkIFBMU0RBLWJhdGNoLgoKIyMjIFBlcmNlbnRpbGUgTm9ybWFsaXNhdGlvbgoKRm9yIFBOIGNvcnJlY3Rpb24sIGxldCdzIGFwcGx5IGBwZXJjZW50aWxlX25vcm0oKWAgZnVuY3Rpb24gZnJvbSAqUExTREFiYXRjaCogcGFja2FnZSBhbmQgc3BlY2lmeSBhIGNvbnRyb2wgZ3JvdXAgKGBjdHJsLmdycGApLgoKCmBgYHtyfQphZC5QTiA8LSBwZXJjZW50aWxlX25vcm0oZGF0YSA9IGFkLmNsciwgYmF0Y2ggPSBhZC5iYXRjaCwgCiAgICAgICAgICAgICAgICAgICAgICAgICB0cnQgPSBhZC50cnQsIGN0cmwuZ3JwID0gJzAtMC41JykKYGBgCgojIyMgUlVWSUlJIHdpdGggcHNldWRvIHJlcGxpY2F0ZXMKCmBgYHtyfQojIGFkZCBncm91cCBtZWFuIGFzIHRoZSBwc2V1ZG8gcmVwbGljYXRlcwphZF9ncm91cF9tZWFuIDwtIG1hdHJpeChuY29sID0gbmNvbChhZC5jbHIpKQphZF9tZWFuX3RydF9sYWIgPC0gYygpCmFkX21lYW5fYmF0Y2hfbGFiIDwtIGMoKQpmb3IodGUgaW4gbGV2ZWxzKGFkLnRydCkpewogIGZvcihiZSBpbiBsZXZlbHMoYWQuYmF0Y2gpKXsKICAgIGFkX2dyb3VwX21lYW4gPC0gcmJpbmQoYWRfZ3JvdXBfbWVhbixhcHBseShhZC5jbHJbYWQudHJ0ID09IHRlICYgYWQuYmF0Y2ggPT0gYmUsIF0sIDIsIG1lYW4pKQogICAgYWRfbWVhbl90cnRfbGFiIDwtIGMoYWRfbWVhbl90cnRfbGFiLCB0ZSkKICAgIGFkX21lYW5fYmF0Y2hfbGFiIDwtIGMoYWRfbWVhbl9iYXRjaF9sYWIsIGJlKQogIH0KfQphZF9ncm91cF9tZWFuIDwtIGFkX2dyb3VwX21lYW5bLTEsXQphZF9uZXdfaW5kIDwtIHJlcChjKCJtZWFuMSIsICJtZWFuMiIpLCBlYWNoID0gNSkKCgphZC5yZXBsaWNhdGVzLm5ldyA8LSBhcy5mYWN0b3IoYyhhcy5jaGFyYWN0ZXIoYWQubWV0YWRhdGEkc2FtcGxlX25hbWUuZGF0YS5leHRyYWN0aW9uKSwgYWRfbmV3X2luZCkpCmFkLnJlcGxpY2F0ZXMubWF0cml4Lm5ldyA8LSByZXBsaWNhdGUubWF0cml4KGFkLnJlcGxpY2F0ZXMubmV3KQphZC5jbHIubmV3IDwtIHJiaW5kKGFkLmNsciwgYWRfZ3JvdXBfbWVhbikKYWQudHJ0Lm5ldyA8LSBhcy5mYWN0b3IoYyhhcy5jaGFyYWN0ZXIoYWQudHJ0KSwgYWRfbWVhbl90cnRfbGFiKSkKYWQuYmF0Y2gubmV3IDwtIGFzLmZhY3RvcihjKGFzLmNoYXJhY3RlcihhZC5iYXRjaCksIGFkX21lYW5fYmF0Y2hfbGFiKSkKCiMgZW1waXJpY2FsIG5lZ2F0aXZlIGNvbnRyb2xzCmFkLmVtcGlyLnAubmV3IDwtIGMoKQpmb3IoZSBpbiBzZXFfbGVuKG5jb2woYWQuY2xyLm5ldykpKXsKICBhZC5lbXBpci5sbSA8LSBsbShhZC5jbHIubmV3WyxlXSB+IGFkLnRydC5uZXcpCiAgYWQuZW1waXIucC5uZXdbZV0gPC0gc3VtbWFyeShhZC5lbXBpci5sbSkkY29lZmZpY2llbnRzWzIsNF0KfQphZC5lbXBpci5wLmFkai5uZXcgPC0gcC5hZGp1c3QocCA9IGFkLmVtcGlyLnAubmV3LCBtZXRob2QgPSAnZmRyJykKYWQubmMubmV3IDwtIGFkLmVtcGlyLnAuYWRqLm5ldyA+IDAuMDUKCiMgZXN0aW1hdGUgawphZC5rLnJlcy5uZXcgPC0gZ2V0SyhZID0gYWQuY2xyLm5ldywgWCA9IGFkLnRydC5uZXcsIGN0bCA9IGFkLm5jLm5ldykKYWQuay5uZXcgPC0gYWQuay5yZXMubmV3JGsKCiMgUlVWSUlJCmFkLlJVVklJSS5uZXcgPC0gUlVWSUlJKFkgPSBhZC5jbHIubmV3LCAKICAgICAgICAgICAgICAgICAgICBNID0gYWQucmVwbGljYXRlcy5tYXRyaXgubmV3LCAKICAgICAgICAgICAgICAgICAgICBjdGwgPSBhZC5uYy5uZXcsIGsgPSBhZC5rLm5ldykKcm93bmFtZXMoYWQuUlVWSUlJLm5ldykgPC0gcm93bmFtZXMoYWQuY2xyLm5ldykKYGBgCgoKIyMgQXNzZXNzaW5nIGJhdGNoIGVmZmVjdCBjb3JyZWN0aW9uCgoKIyMjIFBDQQoKRmlyc3QsIHdlIGNhbiBjb21wYXJlIHRoZSBQQ0Egc2FtcGxlIHBsb3RzIGJlZm9yZSBhbmQgYWZ0ZXIgYmF0Y2ggZWZmZWN0IGNvcnJlY3Rpb24gd2l0aCBkaWZmZXJlbnQgbWV0aG9kcy4KCmBgYHtyfQphZC5wY2EuYmVmb3JlIDwtIHBjYShhZC5jbHIsIG5jb21wID0gMywgc2NhbGUgPSBUUlVFKQphZC5wY2EuckJFIDwtIHBjYShhZC5yQkUsIG5jb21wID0gMywgc2NhbGUgPSBUUlVFKQphZC5wY2EuQ29tQmF0IDwtIHBjYShhZC5Db21CYXQsIG5jb21wID0gMywgc2NhbGUgPSBUUlVFKQphZC5wY2EuUExTREFfYmF0Y2ggPC0gcGNhKGFkLlBMU0RBX2JhdGNoLCBuY29tcCA9IDMsIHNjYWxlID0gVFJVRSkKYWQucGNhLnNQTFNEQV9iYXRjaCA8LSBwY2EoYWQuc1BMU0RBX2JhdGNoLCBuY29tcCA9IDMsIHNjYWxlID0gVFJVRSkKYWQucGNhLlBOIDwtIHBjYShhZC5QTiwgbmNvbXAgPSAzLCBzY2FsZSA9IFRSVUUpCmFkLnBjYS5SVVZJSUkgPC0gcGNhKGFkLlJVVklJSSwgbmNvbXAgPSAzLCBzY2FsZSA9IFRSVUUpCmFkLnBjYS5SVVZJSUkubmV3IDwtIHBjYShhZC5SVVZJSUkubmV3LCBuY29tcCA9IDMsIHNjYWxlID0gVFJVRSkKYGBgCgpgYGB7ciwgZmlnLnNob3c9J2hpZGUnfQojIG9yZGVyIGJhdGNoZXMKYWQuYmF0Y2ggPSBmYWN0b3IoYWQubWV0YWRhdGEkc2VxdWVuY2luZ19ydW5fZGF0ZSwgCiAgICAgICAgICAgICAgICAgIGxldmVscyA9IHVuaXF1ZShhZC5tZXRhZGF0YSRzZXF1ZW5jaW5nX3J1bl9kYXRlKSkKCmFkLnBjYS5iZWZvcmUucGxvdCA8LSBTY2F0dGVyX0RlbnNpdHkob2JqZWN0ID0gYWQucGNhLmJlZm9yZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmF0Y2ggPSBhZC5iYXRjaCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJ0ID0gYWQudHJ0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aXRsZSA9ICdCZWZvcmUgY29ycmVjdGlvbicpCmFkLnBjYS5yQkUucGxvdCA8LSBTY2F0dGVyX0RlbnNpdHkob2JqZWN0ID0gYWQucGNhLnJCRSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmF0Y2ggPSBhZC5iYXRjaCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJ0ID0gYWQudHJ0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aXRsZSA9ICdyZW1vdmVCYXRjaEVmZmVjdCcpCmFkLnBjYS5Db21CYXQucGxvdCA8LSBTY2F0dGVyX0RlbnNpdHkob2JqZWN0ID0gYWQucGNhLkNvbUJhdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmF0Y2ggPSBhZC5iYXRjaCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJ0ID0gYWQudHJ0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aXRsZSA9ICdDb21CYXQnKQphZC5wY2EuUExTREFfYmF0Y2gucGxvdCA8LSBTY2F0dGVyX0RlbnNpdHkob2JqZWN0ID0gYWQucGNhLlBMU0RBX2JhdGNoLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJhdGNoID0gYWQuYmF0Y2gsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJ0ID0gYWQudHJ0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpdGxlID0gJ1BMU0RBLWJhdGNoJykKYWQucGNhLnNQTFNEQV9iYXRjaC5wbG90IDwtIFNjYXR0ZXJfRGVuc2l0eShvYmplY3QgPSBhZC5wY2Euc1BMU0RBX2JhdGNoLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYXRjaCA9IGFkLmJhdGNoLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cnQgPSBhZC50cnQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpdGxlID0gJ3NQTFNEQS1iYXRjaCcpCmFkLnBjYS5QTi5wbG90IDwtIFNjYXR0ZXJfRGVuc2l0eShvYmplY3QgPSBhZC5wY2EuUE4sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmF0Y2ggPSBhZC5iYXRjaCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cnQgPSBhZC50cnQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGl0bGUgPSAnUGVyY2VudGlsZSBOb3JtYWxpc2F0aW9uJykKYWQucGNhLlJVVklJSS5wbG90IDwtIFNjYXR0ZXJfRGVuc2l0eShvYmplY3QgPSBhZC5wY2EuUlVWSUlJLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYXRjaCA9IGFkLmJhdGNoLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cnQgPSBhZC50cnQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpdGxlID0gJ1JVVklJSScpCmFkLnBjYS5SVVZJSUkubmV3LnBsb3QgPC0gU2NhdHRlcl9EZW5zaXR5KG9iamVjdCA9IGFkLnBjYS5SVVZJSUkubmV3LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYXRjaCA9IGFkLmJhdGNoLm5ldywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJ0ID0gYWQudHJ0Lm5ldywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGl0bGUgPSAnUlVWSUlJK3BzZVInKQoKYGBgCgpgYGB7ciBBRHBjYTIsIGZpZy5oZWlnaHQgPSAxNywgZmlnLndpZHRoID0gMTIsIG91dC53aWR0aCA9ICcxMDAlJywgZWNobyA9IEZBTFNFLCBmaWcuYWxpZ24gPSAnY2VudGVyJywgZmlnLmNhcCA9ICdUaGUgUENBIHNhbXBsZSBwbG90cyB3aXRoIGRlbnNpdGllcyBiZWZvcmUgYW5kIGFmdGVyIGJhdGNoIGVmZmVjdCBjb3JyZWN0aW9uIGluIHRoZSBBRCBkYXRhLid9CmdyaWQuYXJyYW5nZShhZC5wY2EuYmVmb3JlLnBsb3QsIGFkLnBjYS5yQkUucGxvdCwgCiAgICAgICAgICAgICBhZC5wY2EuQ29tQmF0LnBsb3QsIGFkLnBjYS5QTFNEQV9iYXRjaC5wbG90LCAKICAgICAgICAgICAgIGFkLnBjYS5zUExTREFfYmF0Y2gucGxvdCwgYWQucGNhLlBOLnBsb3QsIAogICAgICAgICAgICAgYWQucGNhLlJVVklJSS5wbG90LCBhZC5wY2EuUlVWSUlJLm5ldy5wbG90LAogICAgICAgICAgICAgbmNvbCA9IDIpCmBgYAoKIyMjIHBSREEKCmBgYHtyIEFEcHJkYTIsIGZpZy5oZWlnaHQgPSA2LCBmaWcuYWxpZ24gPSAnY2VudGVyJywgZmlnLmNhcCA9ICdHbG9iYWwgZXhwbGFpbmVkIHZhcmlhbmNlIGJlZm9yZSBhbmQgYWZ0ZXIgYmF0Y2ggZWZmZWN0IGNvcnJlY3Rpb24gZm9yIHRoZSBBRCBkYXRhLCB3aGVyZSBSVVZJSUkgdXNlcyBwc2V1ZG8gcmVwbGljYXRlcy4nfQoKYWQuY29ycmVjdGVkLmxpc3QgPC0gbGlzdChgQmVmb3JlIGNvcnJlY3Rpb25gID0gYWQuY2xyLCAKICAgICAgICAgICAgICAgICAgICAgICAgICByZW1vdmVCYXRjaEVmZmVjdCA9IGFkLnJCRSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgQ29tQmF0ID0gYWQuQ29tQmF0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICBgUExTREEtYmF0Y2hgID0gYWQuUExTREFfYmF0Y2gsIAogICAgICAgICAgICAgICAgICAgICAgICAgIGBzUExTREEtYmF0Y2hgID0gYWQuc1BMU0RBX2JhdGNoLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBgUGVyY2VudGlsZSBOb3JtYWxpc2F0aW9uYCA9IGFkLlBOLAogICAgICAgICAgICAgICAgICAgICAgICAgIFJVVklJSSA9IGFkLlJVVklJSSkKCmFkLnByb3AuZGYgPC0gZGF0YS5mcmFtZShUcmVhdG1lbnQgPSBOQSwgQmF0Y2ggPSBOQSwgCiAgICAgICAgICAgICAgICAgICAgICAgICBJbnRlcnNlY3Rpb24gPSBOQSwgCiAgICAgICAgICAgICAgICAgICAgICAgICBSZXNpZHVhbHMgPSBOQSkgCmZvcihpIGluIHNlcV9sZW4obGVuZ3RoKGFkLmNvcnJlY3RlZC5saXN0KSkpewogIHJkYS5yZXMgPSB2YXJwYXJ0KGFkLmNvcnJlY3RlZC5saXN0W1tpXV0sIH4gdHJ0LCB+IGJhdGNoLAogICAgICAgICAgICAgICAgICAgIGRhdGEgPSBhZC5mYWN0b3JzLmRmLCBzY2FsZSA9IFRSVUUpCiAgYWQucHJvcC5kZltpLCBdIDwtIHJkYS5yZXMkcGFydCRpbmRmcmFjdCRBZGouUi5zcXVhcmVkfQoKcm93bmFtZXMoYWQucHJvcC5kZikgPSBuYW1lcyhhZC5jb3JyZWN0ZWQubGlzdCkKCmFkLnByb3AuZGYgPC0gYWQucHJvcC5kZlssIGMoMSwzLDIsNCldCgphZC5wcm9wLmRmW2FkLnByb3AuZGYgPCAwXSA9IDAKYWQucHJvcC5kZiA8LSBhcy5kYXRhLmZyYW1lKHQoYXBwbHkoYWQucHJvcC5kZiwgMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uKHgpe3gvc3VtKHgpfSkpKQoKIyBSVVZJSUkgKyBwc2V1ZG8gcmVwbGljYXRlcwpyZGEucnV2aWlpID0gdmFycGFydChhZC5SVVZJSUkubmV3LCB+IGFkLnRydC5uZXcsIH4gYWQuYmF0Y2gubmV3LCBzY2FsZSA9IFRSVUUpClJVVklJSS5wcm9wID0gcmRhLnJ1dmlpaSRwYXJ0JGluZGZyYWN0JEFkai5SLnNxdWFyZWRbYygxLDMsMiw0KV0KClJVVklJSS5wcm9wW1JVVklJSS5wcm9wIDwgMF0gPSAwClJVVklJSS5wcm9wIDwtIFJVVklJSS5wcm9wL3N1bShSVVZJSUkucHJvcCkKCmFkLnByb3AuZGYgPC0gcmJpbmQoYWQucHJvcC5kZiwgYFJVVklJSStwc2VSYCA9IFJVVklJSS5wcm9wKQoKcGFydFZhcl9wbG90KHByb3AuZGYgPSBhZC5wcm9wLmRmKQpgYGAKCgojIFNlc3Npb24gSW5mbwoKYGBge3J9CnNlc3Npb25JbmZvKCkKYGBgCgojIFJlZmVyZW5jZXMKCgoKCg==